OkHttp實現多執行緒併發下載的筆記
打個廣告,不瞭解OkHttp的話可以先看下
http://blog.csdn.net/brycegao321/article/details/51830525
需求:
手機拍攝若干張照片, 在wifi連線下上傳到伺服器。
考慮問題: 如何設定併發傳遞多個檔案的數量? 先劇透一下, OkHttp預設支援併發5個相同ip地址的上傳檔案請求。
OkHttp是通過 client.newCall(request).enqueue函式新增非同步任務的, newCall函式從字面上就看出是建立一個Call例項, 而類Call是OkHttp的執行單元, 即每個Http請求/返回都是一個Call物件。
/** * A call is a request that has been prepared for execution. A call can be * canceled. As this object represents a single request/response pair (stream), * it cannot be executed twice. */再看看enqueue函式(執行緒安全的哦)做了什麼:
public void enqueue(Callback responseCallback) { enqueue(responseCallback, falseDispatcher是OkHttp的排程策略類(Policy on when async requests are executed), 跟進去看Dispatcher的enqueue函式:); } void enqueue(Callback responseCallback, boolean forWebSocket) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); }
synchronized void我們看到這裡的maxRequests(預設64)和maxRequestsPerHost(預設5)變數, 從英文字面上也能理解出來是最大連線數和同host的最大連線數, 而runningCalls是正在執行的請求, readyCalls是等待執行的請求。getExecutorService().execute(call)是真正的執行http互動。enqueue(AsyncCall call) { if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningCalls.add(call); getExecutorService().execute(call); } else { readyCalls.add(call); } }
private int maxRequests = 64; private int maxRequestsPerHost = 5;
背景知識:上傳檔案的http協議使用了multipart(一般上傳檔案都使用這種格式, 報文格式可以百度下, 剛好公司伺服器down掉了, 沒法抓包。。。)的方式傳輸檔案位元組流, 稍後會貼出相應程式碼。
第一步: 就是簡單的for迴圈封裝各個檔案傳輸時的引數, 例如檔名、經緯度、使用者名稱之類的, 後臺會存到資料庫;
for (int i = 0; i < list.size(); i++) { Photo bean = list.get(i); String filePath = bean.getUrl(); if (filePath != null && bean.getStatus() == 0) { if (filePath.startsWith("file://")) filePath = filePath.replace("file://", ""); File file = new File(filePath); if (!file.exists() || file.isDirectory()) { conitue; }Map<String, Object> map = new HashMap<>(); map.put("userid", bean.getUserId());
NetworkUtils.uploadProgressFile(mContext, ConstantUrls.UPLOAD_IMG, callBack第二步: uploadProgressFile函式, 包含url地址、回撥函式(更新ui進度)。base、param是引數,用於鑑權和傳遞圖片相關欄位, addPart是要真正要上傳檔案位元組流的封裝。 我們看到在writeTo函式裡的listener.onProgress回撥就是通知上層重新整理進度的(注意: 這是在子執行緒執行的哦,不能執行刷UI!)
MediaType MEDIA_TYPE_PNG = MediaType.parse("application/octet-stream"); RequestBody requestBody = new MultipartBuilder() .type(MultipartBuilder.FORM) .addFormDataPart("body", JSON.toJSONString(body)) .addFormDataPart("base", base) .addPart(Headers.of("Content-Disposition", "form-data; name=\"datums\" ;filename=\"" + fileName + "\""), createUploadRequestBody(MEDIA_TYPE_PNG, file, uploadListener, i)) .build();
public static RequestBody createUploadRequestBody(final MediaType contentType, final File file, final ProgressListener listener, final int position) { 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 contentLength = contentLength(); long currentBytes = 0; for (long readCount; (readCount = source.read(buf, 2048)) != -1; ) { sink.write(buf, readCount); currentBytes += readCount; if (listener != null) { listener.onProgress(currentBytes, contentLength, currentBytes == contentLength, position); } } } catch (Exception e) { // e.printStackTrace(); } } }; }
我們看到使用OkHttp併發傳輸檔案的程式碼很簡單, 就是封裝請求後扔到OkHttp的佇列裡, UI響應回撥函式就可以了。
我的微信公眾號, 歡迎關注, 讓我們一起成長
補充下載檔案示例程式碼:
public interface ICallBack {
void onUploadSuccess(String path); //通知成功,並返回檔案位置
void onUploadFailure(); //失敗
void onUploadProgress(int current, int total); //上傳進度
}
public boolean downLoadFile(String fileUrl, String filePath, final ICallBack callBack) {final File file = new File(destFileDir, System.currentTimeMillis()"");
if (file.exists()) {
return false;
}
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());
.... //回撥通知失敗, 當前是在子執行緒
callBack.onUploadFailure();
}@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);
callBack.onUploadProgress(current, total);
}
fos.flush();
callBack.onUploadSuccess(file.getAbsolutePath());
} catch (IOException e) {
Log.e(TAG, e.toString());
callBack.onUploadFailure(); //回撥通知失敗, 當前在子執行緒
} finally {
try {
if (is != null) {
is.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {Log.e(TAG, e.toString());
}
}
}
});
}