本文參考自【http://blog.csdn.net/crazy__chen/article/details/47703781】
在Android 2.3及以上版本,使用的是HttpURLConnection,而在Android 2.2及以下版本,使用的是HttpClient
HttpClient
DefaultHttpClient和它的兄弟AndroidHttpClient都是HttpClient具體的實現類,它們都擁有眾多的API,而且實現比較穩定,bug數量也很少。
但同時也由于HttpClient的API數量過多,使得我們很難在不破壞兼容性的情況下對它進行升級和擴展,所以目前Android團隊在提升和優化HttpClient方面的工作態度并不積極。
HttpURLConnection
HttpURLConnection是一種多用途、輕量極的HTTP客戶端,使用它來進行HTTP操作可以適用于大多數的應用程序。雖然HttpURLConnection的API提供的比較簡單,但是同時這也使得我們可以更加容易地去使用和擴展它。
不過在Android 2.2版本之前,HttpURLConnection一直存在著一些令人厭煩的bug。比如說對一個可讀的InputStream調用close()方法時,就有可能會導致連接池失效了。那么我們通常的解決辦法就是直接禁用掉連接池的功能:
private void disableConnectionReuseIfNecessary() {
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
在Android 2.3版本的時候,我們加入了更加透明化的響應壓縮。HttpURLConnection會自動在每個發出的請求中加入如下消息頭,并處理相應的返回結果:?
Accept-Encoding: gzip
配置你的Web服務器來支持對客戶端的響應進行壓縮的功能,從而可以在這一改進上獲取到最大的好處。如果在壓縮響應的時候出現了問題,這篇文檔會告訴你如何禁用掉這個功能。
但是如果啟動了響應壓縮的功能,HTTP響應頭里的Content-Length就會代表著壓縮后的長度,這時再使用getContentLength()方法來取出解壓后的數據就是錯誤的了。正確的做法應該是一直調用InputStream.read()方法來讀取響應數據,一直到出現-1為止。
我們在Android 2.3版本中還增加了一些HTTPS方面的改進,現在HttpsURLConnection會使用SNI(Server Name Indication)的方式進行連接,使得多個HTTPS主機可以共享同一個IP地址。除此之外,還增加了一些壓縮和會話的機制。如果連接失敗,它會自動去嘗試重新進行連接。這使得HttpsURLConnection可以在不破壞老版本兼容性的前提下,更加高效地連接最新的服務器。
在Android 4.0版本中,我們又添加了一些響應的緩存機制。當緩存被安裝后(調用HttpResponseCache的install()方法),所有的HTTP請求都會滿足以下三種情況:
所有的緩存響應都由本地存儲來提供。因為沒有必要去發起任務的網絡連接請求,所有的響應都可以立刻獲取到。
視情況而定的緩存響應必須要有服務器來進行更新檢查。比如說客戶端發起了一條類似于 “如果/foo.png這張圖片發生了改變,就將它發送給我” 這樣的請求,服務器需要將更新后的數據進行返回,或者返回一個304 Not Modified狀態。如果請求的內容沒有發生,客戶端就不會下載任何數據。
沒有緩存的響應都是由服務器直接提供的。這部分響應會在稍后存儲到響應緩存中。
由于這個功能是在4.0之后的版本才有的,通常我們就可以使用反射的方式來啟動響應緩存功能。下面的示例代碼展示了如何在Android 4.0及以后的版本中去啟用響應緩存的功能,同時還不會影響到之前的版本:
在當前Android 6.0 版本已經刪除了HttpClient相關API,寫相應程序時建議大家選擇HttpConnection.
文件上傳是常見功能,然而Android網上大多數的文件上傳都使用httpclient,而且需要添加一個httpmine-jar,其實HttpURLConnection也可以實現文件上傳,但是它在移動端有個弊端,就是不能上傳大文件,所以這次說的方式,只能上傳一些較小的文件。
文件上傳,并且帶上一些參數,這需要我們了解http請求的構造方式,也就是它的格式。
HttpURLConnection需要我們自己構造請求頭部,也就是我們要拼接出一個正確完整的請求。
下面來看一個典型的例子
[java]?view plaincopy
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--??
上面的例子中,我們首先看
[java]?view plaincopy
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來進行換行
然后就是參數內容部分了,先來看
[java]?view plaincopy
--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,我們看下一個參數,也是同理
[java]?view plaincopy
--OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp?? Content-Disposition:?form-data;?name="param2"?? Content-Type:?text/plain;?charset=UTF-8?? Content-Transfer-Encoding:?8bit?? ?? "nihao"??
然后下一個參數,就是文件了
雖然指定的內容不一樣,但是格式是一樣的
[java]?view plaincopy
--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,大家仔細看上面的格式,不能出一點差錯,因為格式不對,就上傳不了了。
接下來,我們直接看我寫的一個帶參數文件上傳工具類
[java]?view plaincopy
? ? ?? 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?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);?? ????????????????? ? ? ? ?? ????????????????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();?? ????????????????? ? ? ?? ????????????????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);?? ????}?? }??
使用方式是這樣的:
[java]?view plaincopy
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);????? ????????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();?????????? ????}????? } ?
總結
以上是生活随笔為你收集整理的android使用HttpURLConnection/HttpClient实现带参数文件上传的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。