1. 程式人生 > >android使用HttpURLConnection實現帶引數檔案上傳

android使用HttpURLConnection實現帶引數檔案上傳

檔案上傳是常見功能,然而android網上大多數的檔案上傳都使用httpclient,而且需要新增一個httpmine-jar,其實HttpURLConnection也可以實現檔案上傳,但是它在移動端有個弊端,就是不能上傳大檔案,所以這次說的方式,只能上傳一些較小的檔案。

檔案上傳,並且帶上一些引數,這需要我們瞭解http請求的構造方式,也就是它的格式。

HttpURLConnection需要我們自己構造請求頭部,也就是我們要拼接出一個正確完整的請求。

下面來看一個典型的例子

POST /api/feed/ HTTP/1.1
Accept-Encoding: gzip
Content-Length: 225873
Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Host: www.myhost.com
Connection: Keep-Alive

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="param1"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

888
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="param2"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

"nihao"
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

這裡是圖片的二進位制資料
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--

上面的例子中,我們首先看
POST /api/feed/ HTTP/1.1
Accept-Encoding: gzip
Content-Length: 225873
Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Host: www.myhost.com
Connection: Keep-Alive

第一行:為POST方式,要請求的子路徑為/api/feed/,例如我們的伺服器地址為www.myhost.com,然後我們的這個請求的完整路徑就是www.myhost.com/api/feed/,最後說明了HTTP協議的版本號為1.1

第二行:資料壓縮方式

第三行:資料長度

第四行:multipart/form-data;是指上傳的資料型別,這裡是指檔案形式。boundary是我們必須指定的一個分界符,不同引數之間要用這個分界符隔開。而OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp就是具體的分界符,這個引數我們可以自己隨機生成的。

第五行:主機地址

第六行:持久連線,Keep-Alive功能避免了建立或者重新建立連線

第七行:換行,這個換行是必須的,我們使用\r\n來進行換行

然後就是引數內容部分了,先來看

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="param1"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

888
我們把上面的看成一個整體

第一行:我要先用分隔符來宣告一個引數的開始。注意,分隔符前面還加了兩橫“--”,這個也是必須加上的!

第二行:name="param1",其實param1就是傳遞的引數的鍵值,例如在get方式中,我們這樣寫http://www.baidu.com?param1=888

第三行:同樣是內容格式,不過這次是指定傳文字,所以是text/plain;  另外,指定了編碼方式charset=UTF-8

第四行:描述的是訊息請求(request)和響應(response)所附帶的實體物件(entity)的傳輸形式,簡單文字資料我們設定為8bit,檔案引數我們設定為binary就行

第五行:換行,這個是必須的!

第六行:引數值,例如http://www.baidu.com?param1=888,就是888

OK,我們看下一個引數,也是同理

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="param2"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

"nihao"

然後下一個引數,就是檔案了

雖然指定的內容不一樣,但是格式是一樣的

--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
Content-Disposition: form-data; name="images"; filename="/storage/emulated/0/Camera/jdimage/1xh0e3yyfmpr2e35tdowbavrx.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary

這裡是圖片的二進位制資料
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp--

OK,大家仔細看上面的格式,不能出一點差錯,因為格式不對,就上傳不了了。

接下來,我們直接看我寫的一個帶引數檔案上傳工具類

/**
 * Created by kaiyi.cky on 2015/8/16.
 */
public class FileUploader {
    private static final String TAG = "uploadFile";
    private static final int TIME_OUT = 10*10000000; //超時時間
    private static final String CHARSET = "utf-8"; //設定編碼
    private static final String PREFIX = "--";
    private static final String LINE_END = "\r\n";

    public static void upload(String host,File file,Map<String,String> params,FileUploadListener listener){
        String BOUNDARY = UUID.randomUUID().toString(); //邊界標識 隨機生成 String PREFIX = "--" , LINE_END = "\r\n";
        String CONTENT_TYPE = "multipart/form-data"; //內容型別
        try {
            URL url = new URL(host);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(TIME_OUT);
            conn.setConnectTimeout(TIME_OUT);
            conn.setRequestMethod("POST"); //請求方式
            conn.setRequestProperty("Charset", CHARSET);//設定編碼
            conn.setRequestProperty("connection", "keep-alive");
            conn.setRequestProperty("Content-Type", CONTENT_TYPE + ";boundary=" + BOUNDARY);
            conn.setDoInput(true); //允許輸入流
            conn.setDoOutput(true); //允許輸出流
            conn.setUseCaches(false); //不允許使用快取
            if(file!=null) {
                /** * 當檔案不為空,把檔案包裝並且上傳 */
                OutputStream outputSteam=conn.getOutputStream();
                DataOutputStream dos = new DataOutputStream(outputSteam);
                StringBuffer sb = new StringBuffer();
                sb.append(LINE_END);
                if(params!=null){//根據格式,開始拼接文字引數
                    for(Map.Entry<String,String> entry:params.entrySet()){                        
                        sb.append(PREFIX).append(BOUNDARY).append(LINE_END);//分界符
                        sb.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"" + LINE_END);
                        sb.append("Content-Type: text/plain; charset=" + CHARSET + LINE_END);
                        sb.append("Content-Transfer-Encoding: 8bit" + LINE_END);
                        sb.append(LINE_END);
                        sb.append(entry.getValue());
                        sb.append(LINE_END);//換行!
                    }
                }
                sb.append(PREFIX);//開始拼接檔案引數
                sb.append(BOUNDARY); sb.append(LINE_END);
                /**
                 * 這裡重點注意:
                 * name裡面的值為伺服器端需要key 只有這個key 才可以得到對應的檔案
                 * filename是檔案的名字,包含字尾名的 比如:abc.png
                 */
                sb.append("Content-Disposition: form-data; name=\"img\"; filename=\""+file.getName()+"\""+LINE_END);
                sb.append("Content-Type: application/octet-stream; charset="+CHARSET+LINE_END);
                sb.append(LINE_END);
                //寫入檔案資料
                dos.write(sb.toString().getBytes());
                InputStream is = new FileInputStream(file);
                byte[] bytes = new byte[1024];
                long totalbytes = file.length();
                long curbytes = 0;
                Log.i("cky","total="+totalbytes);
                int len = 0;
                while((len=is.read(bytes))!=-1){
                    curbytes += len;
                    dos.write(bytes, 0, len);
                    listener.onProgress(curbytes,1.0d*curbytes/totalbytes);
                }
                is.close();
                dos.write(LINE_END.getBytes());\\一定還有換行
                byte[] end_data = (PREFIX+BOUNDARY+PREFIX+LINE_END).getBytes();
                dos.write(end_data);
                dos.flush();
                /**
                 * 獲取響應碼 200=成功
                 * 當響應成功,獲取響應的流
                 */
                int code = conn.getResponseCode();
                sb.setLength(0);
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                String line;
                while((line=br.readLine())!=null){
                    sb.append(line);
                }
                listener.onFinish(code,sb.toString(),conn.getHeaderFields());
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public interface FileUploadListener{
        public void onProgress(long pro,double precent);
        public void onFinish(int code,String res,Map<String,List<String>> headers);
    }
}

使用方式是這樣的:
public class MainActivity extends FragmentActivity {
   
    File sdDir;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sdDir = null;
        boolean sdCardExist = Environment.getExternalStorageState()
                .equals(Environment.MEDIA_MOUNTED);   //判斷sd卡是否存在
        if(sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();//獲取跟目錄
        }
        final HashMap<String,String> map = new HashMap<String,String>();
        map.put("aa","bb");
        new Thread(){
            @Override
            public void run() {
                FileUploader.upload("上傳地址", new File(sdDir.getPath() + "/檔名"), map, new FileUploader.FileUploadListener() {
                    @Override
                    public void onProgress(long pro, double precent) {
                        Log.i("cky", precent+"");
                    }

                    @Override
                    public void onFinish(int code, String res, Map<String, List<String>> headers) {
                        Log.i("cky", res);
                    }
                });
            }
        }.start();        
    }   
}