1. 程式人生 > >OkHttp實現多執行緒併發下載的筆記

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, false
); } void enqueue(Callback responseCallback, boolean forWebSocket) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } client.getDispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket)); }
          Dispatcher是OkHttp的排程策略類(Policy on when async requests are executed), 跟進去看Dispatcher的enqueue函式:
synchronized void 
enqueue(AsyncCall call) { if (runningCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningCalls.add(call); getExecutorService().execute(call); } else { readyCalls.add(call); } }
        我們看到這裡的maxRequests(預設64)和maxRequestsPerHost(預設5)變數, 從英文字面上也能理解出來是最大連線數和同host的最大連線數, 而runningCalls是正在執行的請求, readyCalls是等待執行的請求。getExecutorService().execute(call)是真正的執行http互動。
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());
                    }
                }
            }
        });
    }