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());
                    }
                }
            }
        });
    }

展开阅读全文

没有更多推荐了,返回首页