使用Android的OkHttp包實(shí)現(xiàn)基于HTTP協(xié)議的文件上傳下載
OkHttp的HTTP連接基礎(chǔ)
雖然在使用 OkHttp 發(fā)送 HTTP 請(qǐng)求時(shí)只需要提供 URL 即可,OkHttp 在實(shí)現(xiàn)中需要綜合考慮 3 種不同的要素來確定與 HTTP 服務(wù)器之間實(shí)際建立的 HTTP 連接。這樣做的目的是為了達(dá)到最佳的性能。
首先第一個(gè)考慮的要素是 URL 本身。URL 給出了要訪問的資源的路徑。比如 URL http://www.baidu.com 所對(duì)應(yīng)的是百度首頁的 HTTP 文檔。在 URL 中比較重要的部分是訪問時(shí)使用的模式,即 HTTP 還是 HTTPS。這會(huì)確定 OkHttp 所建立的是明文的 HTTP 連接,還是加密的 HTTPS 連接。
第二個(gè)要素是 HTTP 服務(wù)器的地址,如 baidu.com。每個(gè)地址都有對(duì)應(yīng)的配置,包括端口號(hào),HTTPS 連接設(shè)置和網(wǎng)絡(luò)傳輸協(xié)議。同一個(gè)地址上的 URL 可以共享同一個(gè)底層 TCP 套接字連接。通過共享連接可以有顯著的性能提升。OkHttp 提供了一個(gè)連接池來復(fù)用連接。
第三個(gè)要素是連接 HTTP 服務(wù)器時(shí)使用的路由。路由包括具體連接的 IP 地址(通過 DNS 查詢來發(fā)現(xiàn))和所使用的代理服務(wù)器。對(duì)于 HTTPS 連接還包括通訊協(xié)商時(shí)使用的 TLS 版本。對(duì)于同一個(gè)地址,可能有多個(gè)不同的路由。OkHttp 在遇到訪問錯(cuò)誤時(shí)會(huì)自動(dòng)嘗試備選路由。
當(dāng)通過 OkHttp 來請(qǐng)求某個(gè) URL 時(shí),OkHttp 首先從 URL 中得到地址信息,再?gòu)倪B接池中根據(jù)地址來獲取連接。如果在連接池中沒有找到連接,則選擇一個(gè)路由來嘗試連接。嘗試連接需要通過 DNS 查詢來得到服務(wù)器的 IP 地址,也會(huì)用到代理服務(wù)器和 TLS 版本等信息。當(dāng)實(shí)際的連接建立之后,OkHttp 發(fā)送 HTTP 請(qǐng)求并獲取響應(yīng)。當(dāng)連接出現(xiàn)問題時(shí),OkHttp 會(huì)自動(dòng)選擇另外的路由進(jìn)行嘗試。這使得 OkHttp 可以自動(dòng)處理可能出現(xiàn)的網(wǎng)絡(luò)問題。當(dāng)成功獲取到 HTTP 請(qǐng)求的響應(yīng)之后,當(dāng)前的連接會(huì)被放回到連接池中,提供給后續(xù)的請(qǐng)求來復(fù)用。連接池會(huì)定期把閑置的連接關(guān)閉以釋放資源。
文件上傳和下載實(shí)例:
1.不帶參數(shù)上傳文件
/** * 上傳文件 * @param actionUrl 接口地址 * @param filePath 本地文件地址 */ public <T> void upLoadFile(String actionUrl, String filePath, final ReqCallBack<T> callBack) { //補(bǔ)全請(qǐng)求地址 String requestUrl = String.format("%s/%s", BASE_URL, actionUrl); //創(chuàng)建File File file = new File(filePath); //創(chuàng)建RequestBody RequestBody body = RequestBody.create(MEDIA_OBJECT_STREAM, file); //創(chuàng)建Request final Request request = new Request.Builder().url(requestUrl).post(body).build(); final Call call = mOkHttpClient.newBuilder().writeTimeout(50, TimeUnit.SECONDS).build().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, e.toString()); failedCallBack("上傳失敗", callBack); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String string = response.body().string(); Log.e(TAG, "response ----->" + string); successCallBack((T) string, callBack); } else { failedCallBack("上傳失敗", callBack); } } }); }
2.帶參數(shù)上傳文件
/** *上傳文件 * @param actionUrl 接口地址 * @param paramsMap 參數(shù) * @param callBack 回調(diào) * @param <T> */ public <T>void upLoadFile(String actionUrl, HashMap<String, Object> paramsMap, final ReqCallBack<T> callBack) { try { //補(bǔ)全請(qǐng)求地址 String requestUrl = String.format("%s/%s", upload_head, actionUrl); MultipartBody.Builder builder = new MultipartBody.Builder(); //設(shè)置類型 builder.setType(MultipartBody.FORM); //追加參數(shù) for (String key : paramsMap.keySet()) { Object object = paramsMap.get(key); if (!(object instanceof File)) { builder.addFormDataPart(key, object.toString()); } else { File file = (File) object; builder.addFormDataPart(key, file.getName(), RequestBody.create(null, file)); } } //創(chuàng)建RequestBody RequestBody body = builder.build(); //創(chuàng)建Request final Request request = new Request.Builder().url(requestUrl).post(body).build(); //單獨(dú)設(shè)置參數(shù) 比如讀取超時(shí)時(shí)間 final Call call = mOkHttpClient.newBuilder().writeTimeout(50, TimeUnit.SECONDS).build().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, e.toString()); failedCallBack("上傳失敗", callBack); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String string = response.body().string(); Log.e(TAG, "response ----->" + string); successCallBack((T) string, callBack); } else { failedCallBack("上傳失敗", callBack); } } }); } catch (Exception e) { Log.e(TAG, e.toString()); } }
3.帶參數(shù)帶進(jìn)度上傳文件
/** *上傳文件 * @param actionUrl 接口地址 * @param paramsMap 參數(shù) * @param callBack 回調(diào) * @param <T> */ public <T> void upLoadFile(String actionUrl, HashMap<String, Object> paramsMap, final ReqProgressCallBack<T> callBack) { try { //補(bǔ)全請(qǐng)求地址 String requestUrl = String.format("%s/%s", upload_head, actionUrl); MultipartBody.Builder builder = new MultipartBody.Builder(); //設(shè)置類型 builder.setType(MultipartBody.FORM); //追加參數(shù) for (String key : paramsMap.keySet()) { Object object = paramsMap.get(key); if (!(object instanceof File)) { builder.addFormDataPart(key, object.toString()); } else { File file = (File) object; builder.addFormDataPart(key, file.getName(), createProgressRequestBody(MEDIA_OBJECT_STREAM, file, callBack)); } } //創(chuàng)建RequestBody RequestBody body = builder.build(); //創(chuàng)建Request final Request request = new Request.Builder().url(requestUrl).post(body).build(); final Call call = mOkHttpClient.newBuilder().writeTimeout(50, TimeUnit.SECONDS).build().newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, e.toString()); failedCallBack("上傳失敗", callBack); } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String string = response.body().string(); Log.e(TAG, "response ----->" + string); successCallBack((T) string, callBack); } else { failedCallBack("上傳失敗", callBack); } } }); } catch (Exception e) { Log.e(TAG, e.toString()); } }
4.創(chuàng)建帶進(jìn)度RequestBody
/** * 創(chuàng)建帶進(jìn)度的RequestBody * @param contentType MediaType * @param file 準(zhǔn)備上傳的文件 * @param callBack 回調(diào) * @param <T> * @return */ public <T> RequestBody createProgressRequestBody(final MediaType contentType, final File file, final ReqProgressCallBack<T> callBack) { return new RequestBody() { @Override public MediaType contentType() { return contentType; } @Override public long contentLength() { return file.length(); } @Override public void writeTo(BufferedSink sink) throws IOException { Source source; try { source = Okio.source(file); Buffer buf = new Buffer(); long remaining = contentLength(); long current = 0; for (long readCount; (readCount = source.read(buf, 2048)) != -1; ) { sink.write(buf, readCount); current += readCount; Log.e(TAG, "current------>" + current); progressCallBack(remaining, current, callBack); } } catch (Exception e) { e.printStackTrace(); } } }; }
5.不帶進(jìn)度文件下載
/** * 下載文件 * @param fileUrl 文件url * @param destFileDir 存儲(chǔ)目標(biāo)目錄 */ public <T> void downLoadFile(String fileUrl, final String destFileDir, final ReqCallBack<T> callBack) { final String fileName = MD5.encode(fileUrl); final File file = new File(destFileDir, fileName); if (file.exists()) { successCallBack((T) file, callBack); return; } final Request request = new Request.Builder().url(fileUrl).build(); final Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, e.toString()); failedCallBack("下載失敗", callBack); } @Override public void onResponse(Call call, Response response) throws IOException { InputStream is = null; byte[] buf = new byte[2048]; int len = 0; FileOutputStream fos = null; try { long total = response.body().contentLength(); Log.e(TAG, "total------>" + total); long current = 0; is = response.body().byteStream(); fos = new FileOutputStream(file); while ((len = is.read(buf)) != -1) { current += len; fos.write(buf, 0, len); Log.e(TAG, "current------>" + current); } fos.flush(); successCallBack((T) file, callBack); } catch (IOException e) { Log.e(TAG, e.toString()); failedCallBack("下載失敗", callBack); } finally { try { if (is != null) { is.close(); } if (fos != null) { fos.close(); } } catch (IOException e) { Log.e(TAG, e.toString()); } } } }); }
6.帶進(jìn)度文件下載
/** * 下載文件 * @param fileUrl 文件url * @param destFileDir 存儲(chǔ)目標(biāo)目錄 */ public <T> void downLoadFile(String fileUrl, final String destFileDir, final ReqProgressCallBack<T> callBack) { final String fileName = MD5.encode(fileUrl); final File file = new File(destFileDir, fileName); if (file.exists()) { successCallBack((T) file, callBack); return; } final Request request = new Request.Builder().url(fileUrl).build(); final Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.e(TAG, e.toString()); failedCallBack("下載失敗", callBack); } @Override public void onResponse(Call call, Response response) throws IOException { InputStream is = null; byte[] buf = new byte[2048]; int len = 0; FileOutputStream fos = null; try { long total = response.body().contentLength(); Log.e(TAG, "total------>" + total); long current = 0; is = response.body().byteStream(); fos = new FileOutputStream(file); while ((len = is.read(buf)) != -1) { current += len; fos.write(buf, 0, len); Log.e(TAG, "current------>" + current); progressCallBack(total, current, callBack); } fos.flush(); successCallBack((T) file, callBack); } catch (IOException e) { Log.e(TAG, e.toString()); failedCallBack("下載失敗", callBack); } finally { try { if (is != null) { is.close(); } if (fos != null) { fos.close(); } } catch (IOException e) { Log.e(TAG, e.toString()); } } } }); }7.接口ReqProgressCallBack.java實(shí)現(xiàn)
public interface ReqProgressCallBack<T> extends ReqCallBack<T>{ /** * 響應(yīng)進(jìn)度更新 */ void onProgress(long total, long current); }
/** * 統(tǒng)一處理進(jìn)度信息 * @param total 總計(jì)大小 * @param current 當(dāng)前進(jìn)度 * @param callBack * @param <T> */ private <T> void progressCallBack(final long total, final long current, final ReqProgressCallBack<T> callBack) { okHttpHandler.post(new Runnable() { @Override public void run() { if (callBack != null) { callBack.onProgress(total, current); } } }); }
相關(guān)文章
Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Android實(shí)現(xiàn)加載時(shí)提示“正在加載,請(qǐng)稍后”的方法
在現(xiàn)在的很多應(yīng)用中,當(dāng)在加載的時(shí)候,如果頁面動(dòng)態(tài)數(shù)據(jù)較多,會(huì)有很長(zhǎng)一段時(shí)間的空白頁面,如果加上這個(gè)頁面正在加載的提示,使得應(yīng)用更加人性化。這篇文章就給大家分享了在 Android實(shí)現(xiàn)加載時(shí)提示“正在加載,請(qǐng)稍后”的方法,有需要的朋友們可以參考借鑒。2016-10-10android開發(fā)教程之startActivityForResult使用方法
這篇文章主要介紹了android開發(fā)教程之startActivityForResult使用方法,需要的朋友可以參考下2014-03-03android輸入框內(nèi)容改變的監(jiān)聽事件實(shí)例
下面小編就為大家分享一篇android輸入框內(nèi)容改變的監(jiān)聽事件實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-02-02Android自定義ImageView實(shí)現(xiàn)點(diǎn)擊兩張圖片切換效果
這篇文章主要為大家詳細(xì)介紹了Android自定義ImageView實(shí)現(xiàn)點(diǎn)擊兩張圖片切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android BroadcastReceiver廣播注冊(cè)方式總結(jié)
這篇文章主要介紹了Android BroadcastReceiver廣播注冊(cè)方式總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-01-01Android自定義實(shí)現(xiàn)淘寶下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android自定義實(shí)現(xiàn)淘寶下拉刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12