okhttp执行流程原理分析,okio基本使用,扩展:断点下载的使用

3,025 阅读8分钟

1. 在设计一个网络请求框架时,需要考虑哪些问题?

  • 网络请求是耗时的,在同一个时间段可能涉及多个请求,-->开线程,线程池管理
  • 网络连接,通信,数据读写-->IO流/socket/httpURLConnection
  • 对请求的拼接
  • 缓存的处理,怎样节省资源
  • 扩展性

2. okhttp3的做法

3. okio

最基本的接口只有两个:Sink(writer)、Source(Read),大概相当于OutputStream和InputStream在原生接口中的地位。这两个接口中只定义了一些最基础的IO操作方法

1. 图解okio

okio的骨架。下面是几个核心的类: Okio:提供生成Sink和Source的方法 Sink : 接口类,功能上对应OutputStream Source :接口类,功能上对应InputStream BufferedSink:接口类继承自Sink,内部有一个Buffer的Sink BufferedSource:接口类继承自Source,内部有一个Buffer的Source Buffer:BufferedSink和BufferedSource的最终实现实现类, 实现缓存功能,内部有一个Segment链表 Segment:里面有个byte数组,通过pos,limit控制读写的位置(从byte[]哪里开始读,哪里开始写入),next, prev实现导航到前面或后面的Segment(实现Segment链表结构)

2. 相关连接

  1. csdn

3. socket okio实战

通过一段socket编程来演示:

package com.example.disignmode.myhttp.myokio;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;

import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;

/**
 * describe :
 * date on 2019/5/2
 * author linghailong
 * email 105354999@qq.com
 */
public class OkioClient {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket("127.0.0.1", 8080);
            InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream();
            BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
            BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream));
            writeMsg(bufferedSink,"hello");
            while (true){
                int length=bufferedSource.readInt();
                String message=bufferedSource.readString(length,Charset.forName("utf-8"));
                System.out.println("length is: "+length+" , message is : "+message); if ("error exit".equals(message)) {
                    break;
                }
                String respMsg = getResponseAccordMsg(message);
                writeMsg(bufferedSink, respMsg);
                if ("error exit".equals(respMsg)) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void writeMsg(BufferedSink sink, String msg) {
        try {
            int msgLength = msg.getBytes().length;
            sink.writeInt(msgLength);
            sink.writeString(msg, Charset.forName("utf-8"));
            sink.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static String getResponseAccordMsg(String msg) {
        String result = "";
        if (msg != null && msg.length() > 0) {
            if (msg.equals("hello")) {
                result = "nice to meet you";
            } else if (msg.equals("nice to meet you too")) {
                result = "see you";
            }
        }
        if (result.length() == 0) {
            result = "error exit";
        }
        return result;
    }
}

Server端代码

package com.example.disignmode.myhttp.myokio;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;

import okio.BufferedSink;
import okio.BufferedSource;
import okio.Okio;

/**
 * describe :
 * date on 2019/5/2
 * author linghailong
 * email 105354999@qq.com
 */
public class OkioServer {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(8080);
            while (true) {
                Socket connection = null;
                try {
                    connection = serverSocket.accept();
                    handleClientSocket(connection);
                }catch (IOException ex){
                    ex.printStackTrace();
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void handleClientSocket(Socket socket) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        BufferedSource source = Okio.buffer(Okio.source(socket));
                        BufferedSink sink = Okio.buffer(Okio.sink(socket));
                        int length = source.readInt();
                        String message = source.readString(length, Charset.forName("utf-8"));
                        System.out.println("length is: " + length + " , message is : " + message);
                        if ("error exit".equals(message)) {
                            break;
                        }
                        String responseMsg = getResponseAccordMsg(message);
                        if (responseMsg != null) {
                            int respLength = responseMsg.getBytes().length;
                            sink.writeInt(respLength);
                            sink.writeString(responseMsg, Charset.forName("utf-8"));
                            sink.flush();
                        }
                        if ("error exit".equals(responseMsg)) {
                            break;
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (socket != null) {
                        try {
                            socket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        });
        thread.start();
    }

    private static String getResponseAccordMsg(String msg) {
        String result = "";
        if (msg != null && msg.length() > 0) {
            if (msg.equals("hello")) {
                result = "hello";
            } else if (msg.equals("nice to meet you")) {
                result = "nice to meet you too";
            } else if (msg.equals("see you")) {
                result = "see you next time";
            }
        }
        if (result.length() == 0) {
            result = "error exit";
        }
        return result;
    }

}

4. okhttp整个工作流程:

1. 初始化OkhttpClient


public Builder() {
  dispatcher = new Dispatcher();
  protocols = DEFAULT_PROTOCOLS;
  connectionSpecs = DEFAULT_CONNECTION_SPECS;
  eventListenerFactory = EventListener.factory(EventListener.NONE);
  proxySelector = ProxySelector.getDefault();
  cookieJar = CookieJar.NO_COOKIES;
  socketFactory = SocketFactory.getDefault();
  hostnameVerifier = OkHostnameVerifier.INSTANCE;
  certificatePinner = CertificatePinner.DEFAULT;
  proxyAuthenticator = Authenticator.NONE;
  authenticator = Authenticator.NONE;
  connectionPool = new ConnectionPool();
  dns = Dns.SYSTEM;
  followSslRedirects = true;
  followRedirects = true;
  retryOnConnectionFailure = true;
  connectTimeout = 10_000;
  readTimeout = 10_000;
  writeTimeout = 10_000;
  pingInterval = 0;
}

  • Dispatcher 负责请求线程管理的类
  private int maxRequests = 64; // 最大请求数
  private int maxRequestsPerHost = 5; // 最大连接数
  
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

2. 将请求加入队列

okhttp支持同步和异步: 异步:

  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

dispatcher().enqueue()方法中,会判断当期的请求数量以及host数量 如果没有超过阈值,则加入到runningAsyncCalls中,超过,则加入到readyAsyncCalls中等待,

synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

AsyncCall

最终会执行到AsyncCall 的excute()方法,在这里,真正开始执行请求的流程

 final class AsyncCall extends NamedRunnable {
    private final Callback responseCallback;

    AsyncCall(Callback responseCallback) {
      super("OkHttp %s", redactedUrl());
      this.responseCallback = responseCallback;
    }

    String host() {
      return originalRequest.url().host();
    }

    Request request() {
      return originalRequest;
    }

    RealCall get() {
      return RealCall.this;
    }

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
      // 核心
        Response response = getResponseWithInterceptorChain();
        if (retryAndFollowUpInterceptor.isCanceled()) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          eventListener.callFailed(RealCall.this, e);
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }
  }
 Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
		// 核心
    return chain.proceed(originalRequest);
  }

在实例化RealInterceptorChain之前,需要实例化很多有着具体功能的拦截器,它们分为三类,如图 20190216204222635.png

  • 蓝色块上方是APPLication Interceptor,也就是应用程序拦截器,即开发者自己自定义的拦截器,- 代码中的client.interceptors()获取的就是这类拦截器
  • 蓝色块下方是NetWork Interceptor,也就是网络拦截器,用来观察单个网络请求和响应,只能调用一次proceed方法
  • 蓝色块代表的就是OKHttp提供的拦截器,共有5个,也是我们需要关注的重点

RealInterceptorChain

构造方法

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }


  • RealInterceptorChain.proceed

  @Override 
  public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
 
  /**
  * 依次取出拦截器链表中的每个拦截器去获取Response
  */
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    //如果索引值大于等于拦截器列表大小,就抛出异常,因为后续会出现数组越界的异常
    if (index >= interceptors.size()) throw new AssertionError();
    
    // 记录本方法调用次数
    calls++;

    // 如果已经为该Request创建了stream,就不再继续创建了
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // 如果已经为该Request创建了stream,那该方法只能调用一次
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // 创建新的拦截器链对象,并将index索引+1
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //获取拦截器
    Interceptor interceptor = interceptors.get(index);
    //执行拦截器的intercept方法获取结果,并将新的拦截器链对象传入
    Response response = interceptor.intercept(next);

    // 确保该方法只能调用一次
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }

  • 实例化拦截器链RealInterceptorChain ,但是要注意到其中一个参数index是+1了,不像RealCall中是0;因为只有这样做,下一个拦截器执行拦截器链的proceed方法时,才能根据索引取出下一个拦截器,不至于总是把自己取出来;这样就形成了拦截器链的概念
  • 根据index取出拦截器,因为index每次都+1,所以就能根据拦截器列表添加的顺序取出拦截器,依次取出RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor,CallServerInterceptor(开发者未添加拦截器的情况下)
  • 执行拦截器的intercept方法,并把实例化的RealInterceptorChain传入,这样每个拦截器在intercept方法又能调用RealInterceptorChain的proceed方法,这样循环

3. 拦截器流程

|---RealCall
|   |--- Response getResponseWithInterceptorChain()

  1. getResponseWithInterceptorChain()
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 客户端的所有拦截器
    // 使用了责任链的设计模式 每一个拦截器只处理与他相关的拦截器
    interceptors.addAll(client.interceptors());
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }
  1. 请求完成之后,通过getResponseWithInterceptorChain()方法把请求变成了一个响应。
  2. 分析几个拦截器的作用:
    • RetryAndFollowUpInterceptor: 处理重试的拦截器,首先会处理一些异常,只要不是一些致命的异常,它就会continue,重新发起一次请求(把request给下级),如果是致命的异常,抛给上一层。会处理一些重定向
    • BridgeInterceptor 设置一些通用的请求头:Content-Type,Content-Length,Cookie,做一些返回的处理:如果返回得数据被压缩了,那木采用ZipSource,保存Cookie
    • CacheInterceptor (重点) 在缓存可用的情况下,读取本地的缓存的数据,如果有首先判断有没有缓存陈列,然后判断有没有过期,如果没有过期直接拿缓存,如果过期了需要添加一些之前头部信息,如 conditionName = "If-Modified-Since"
    • ConnectInterceptor findhealthyConnection()找一个连接,首先判断有没有健康的,没有就创建(建立Socket,握手连接,)连接缓存。OkHttp基于原生的Socket.RealConnection
    • CallServerInterceptor :从服务器读数据以及写数据。写头部信息,写body信息
  3. 重定向:
    • 返回码是307
    • 然后从response的Header中获取location
    • 重新请求
  4. Http缓存
    1. Cache-Control(缓存策略)public private no-cache maxage no-sotre
    2. Expires (缓存的过期策略)指明了缓存数据有效的绝对时间,告诉客户端到了这个时间点(比照客户端时间)后本地就作废了
  5. 连接三个核心类:连接复用
    • RealConnection
    • ConnectionPool
  6. no-cache, no-store以及max-age=0辨析
    • no-cache: 相当于是重载
    • no-store相当于是不缓存
    • max-age=0; 相当于是刷新
OkHttpClient okHttpClient = new OkHttpClient();
OkHttpClient newClient = okHttpClient.newBuilder()
               .cache(new Cache(mContext.getCacheDir(), 10240*1024))
               .connectTimeout(20, TimeUnit.SECONDS)
               .readTimeout(20, TimeUnit.SECONDS)
               .build();

缓存相关使用实例

/**
     * 一、无论有无网路都添加缓存。
     * 目前的情况是我们这个要addNetworkInterceptor
     * 这样才有效。经过本人测试(chan)测试有效.
     * 60S后如果没有网络将获取不到数据,显示连接失败
     */
    static Interceptor netInterceptor = new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Response response = chain.proceed(request);
          /*String cacheControl = request.header("Cache-Control");
            if (TextUtils.isEmpty(cacheControl)) {
                cacheControl = "public, max-age=60";
            }*/
            int maxAge = 60;
            return response.newBuilder()
                    .removeHeader("Pragma")// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效
                    .removeHeader("Cache-Control")
                    .header("Cache-Control", "public, max-age=" + maxAge)
                    .build();
        }
    };
    
 File cacheFile = new File(BaseApp.getInstance().getCacheDir(), "caheData");
        //设置缓存大小
        Cache cache = new Cache(cacheFile, DEFAULT_DIR_CACHE);//google建议放到这里
        OkHttpClient client = new OkHttpClient.Builder()
                .retryOnConnectionFailure(true)//连接失败后是否重新连接
                .connectTimeout(15, TimeUnit.SECONDS)//超时时间15S
                .addNetworkInterceptor(cacheInterceptor)//这里大家一定要注意了是addNetworkOnterceptor别搞错了啊。
                .cache(cache)
                .build();

拦截器的调用顺序

  1. 自己的
  2. retry
  3. Bridge
  4. Cache
  5. ConnectInterceptor
  6. CallServer

参考文献

  1. 简书上的分享

5 扩展

5.1 文件上传进度监听

  1. 首先需要找到okhttp中是怎样进行上传的。

首先来看一段基础的代码

 private void uploadFile() {
        // 这个是 Okhttp 上传文件的用法
        String url = "https://api.baidu.com/api/upload";
        File file = new File(Environment.getExternalStorageDirectory(), "test.apk");
        OkHttpClient httpClient = new OkHttpClient();
        // 构建请求 Body , 这个我们之前自己动手写过
        MultipartBody.Builder builder = new MultipartBody.Builder()
                .setType(MultipartBody.FORM);
        builder.addFormDataPart("platform", "android");
        builder.addFormDataPart("file", file.getName(),
                RequestBody.create(MediaType.parse(guessMimeType(file.getAbsolutePath())), file));

        ExMultipartBody exMultipartBody = new ExMultipartBody(builder.build()
                ,new UploadProgressListener(){

            @Override
            public void onProgress(long total, long current) {
                showToast(total,current);
            }
        });

        // 怎么监听上传文件的进度?

        // 构建一个请求
        final Request request = new Request.Builder()
                .url(url)
                .post(exMultipartBody).build();
        // new RealCall 发起请求
        Call call = httpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.e("TAG", response.body().string());
            }
        });
    }

分析流程

|---RealCall
|   |---CallServerInterceptor
|   |--- MultipartBody
|   |   |---writeTo(BufferedSink sink)
  1. 由于MultipartBody 是final类型的方法,我们无法直接拿到这个方法,那木我们就可以考虑静态代理的形式,编写一个扩展类,传入代理。监听到contentlength以及currentLength这两个参数之后,通过编写一个借口回调,就可以拿到值,然后在主线程中进行一些操作就可以完成。
  2. ForwardingSink 这是Sink中的一个代理类。

5.2 断点下载

流程讲解:

大概的流程图如上所示:

|---MainActivity()
|   |---DownloadFacade(Context context) //用于 管理
|   |   |---startDownload(String url,DownloadCallback callback)
|   |   |   |---DownloadDispatcher // okhttp执行请求的类
|   |   |   |   |---startDownload(final String url, final DownloadCallback callback)  //真正执行
|   |   |   |   |   |---DownloadTask // download 线程池的分发类
|   |   |   |   |   |   |---init() //实例化,开始计算哪个线程下载哪部分
|   |   |   |   |   |   |   |---DownloadRunnable(String url, int threadId, long start, long end, long progress, DownloadEntity downloadEntity,DownloadCallback callback) //执行下载的线程
  1. MainActivity 在这里仅仅做实例化以及传入需要下载的对象的链接。
  2. DownloadFacade 做一些实例化的准备,以及下载入口
public class DownloadFacade {
    private static final DownloadFacade sFacade = new DownloadFacade();

    private DownloadFacade(){}

    public static DownloadFacade getFacade() {
        return sFacade;
    }

    public void init(Context context){
        FileManager.manager().init(context);
        DaoManagerHelper.getManager().init(context);
    }

    public void startDownload(String url,DownloadCallback callback){
        DownloadDispatcher.getDispatcher().startDownload(url,callback);
    }

    public void startDownload(String url){
        // DownloadDispatcher.getDispatcher().startDownload(url);
    }
}
  1. DownloadDispatcher okhttp的执行类,同时也是下载任务的管理类,在这里开始执行下载
package com.darren.architect_day28.download;

import com.darren.architect_day28.OkHttpManager;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Response;

final class DownloadDispatcher {

    private static final DownloadDispatcher sDispatcher = new DownloadDispatcher();

    private DownloadDispatcher(){

    }

    public static DownloadDispatcher getDispatcher() {
        return sDispatcher;
    }

    /** Ready async calls in the order they'll be run. */
    private final Deque<DownloadTask> readyTasks = new ArrayDeque<>();

    /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
    private final Deque<DownloadTask> runningTasks = new ArrayDeque<>();

    /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
    private final Deque<DownloadTask> stopTasks = new ArrayDeque<>();

    // 最大只能下载多少个 3 5

    public void startDownload(final String url, final DownloadCallback callback){
        // 获取文件的大小
        Call call = OkHttpManager.getManager().asyncCall(url);

        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                  callback.onFailure(e);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                // 获取文件的大小
                long contentLength = response.body().contentLength();

                if(contentLength <= -1){
                    // 没有获取到文件的大小,
                    // 1. 跟后台商量
                    // 2. 只能采用单线程去下载
                    return;
                }
                // 计算每个线程负责哪一块?
                DownloadTask downloadTask = new DownloadTask(url,contentLength,callback);
                downloadTask.init();

                runningTasks.add(downloadTask);
            }
        });
    }

    public void recyclerTask(DownloadTask downloadTask) {
        runningTasks.remove(downloadTask);
        // 参考 OkHttp 的 Dispatcher 的源码,如果还有需要下载的开始下一个的下载
    }

    public void stopDownload(String url){
        // 这个停止的是不是正在下载的
    }

    // 开个单独的线程去执行 所有下载的回调

}

4 DownloadTask 真正执行断点下载的管理类,为执行断点下载的线程分发任务。这个task执行完毕后从downloaddispatcher中的list中移除任务。


public class DownloadTask {
    private String mUrl;
    private long mContentLength;
    private List<DownloadRunnable> mRunnables;
    // OkHttp 为什么搞一个能被回收的线程池?
    OkHttpClient client = new OkHttpClient();
    /**
     * Executes calls. Created lazily.
     */
    private
    @Nullable
    ExecutorService executorService;
    private volatile int mSucceedNumber;

    private DownloadCallback mCallback;
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "DownThread #" + mCount.getAndIncrement());
        }
    };


    public synchronized ExecutorService executorService() {
        if (executorService == null) {
            executorService = new ThreadPoolExecutor(0, THREAD_SIZE, 30, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), new ThreadFactory() {
                @Override
                public Thread newThread(@NonNull Runnable r) {
                    Thread thread = new Thread(r, "DownloadTask");
                    thread.setDaemon(false);
                    return thread;
                }
            });
        }
        return executorService;
    }

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int THREAD_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

    public DownloadTask(String url, long contentLength, DownloadCallback callback) {
        this.mUrl = url;
        this.mContentLength = contentLength;
        mRunnables = new ArrayList<>();
        this.mCallback = callback;
    }

    /**
     * 初始化
     */
    public void init() {
        for (int i = 0; i < THREAD_SIZE; i++) {
            // 计算出每个线程要下载的内容
            long threadSize = mContentLength / THREAD_SIZE;

            // 初始化的时候 这里要去读取数据库


            long start = i * threadSize;
            long end = (i + threadSize) - 1;

            if (i == THREAD_SIZE - 1) {
                end = mContentLength - 1;
            }

            List<DownloadEntity> entities = DaoManagerHelper.getManager().queryAll(mUrl);
            DownloadEntity downloadEntity = getEntity(i, entities);
            if (downloadEntity == null) {
                downloadEntity = new DownloadEntity(start, end, mUrl, i, 0, mContentLength);
            }

            DownloadRunnable downloadRunnable = new DownloadRunnable(mUrl, i, start, end,
                    downloadEntity.getProgress(), downloadEntity, new DownloadCallback() {

                @Override
                public void onFailure(IOException e) {
                    // 一个apk 下载里面有一个线程异常了,处理异常,把其他线程停止掉
                    mCallback.onFailure(e);
                }

                @Override
                public void onSucceed(File file) {
                    // 线程同步一下,
                    synchronized (DownloadTask.this) {
                        mSucceedNumber += 1;
                        if (mSucceedNumber == THREAD_SIZE) {
                            mCallback.onSucceed(file);
                            DownloadDispatcher.getDispatcher().recyclerTask(DownloadTask.this);
                            // 清楚数据库的这个文件下载存储
                        }
                    }
                }
            });
            // 通过线程池去执行
            executorService().execute(downloadRunnable);
        }
    }

    private DownloadEntity getEntity(int threadId, List<DownloadEntity> entities) {
        for (DownloadEntity entity : entities) {
            if (threadId == entity.getThreadId()) {
                return entity;
            }
        }
        return null;
    }

    public void stop() {
        for (DownloadRunnable runnable : mRunnables) {
            runnable.stop();
        }
    }

5 DownloadRunnable 执行断点下载的线程

public class DownloadRunnable implements Runnable{
    private static final int STATUS_DOWNLOADING = 1;
    private static final int STATUS_STOP = 2;
    private final long start;
    private final long end;
    private final int threadId;
    private final String url;
    private final DownloadCallback mCallback;
    private int mStatus = STATUS_DOWNLOADING;
    private long mProgress = 0;
    private DownloadEntity mDownloadEntity;

    public DownloadRunnable(String url, int threadId, long start, long end, long progress, DownloadEntity downloadEntity,DownloadCallback callback) {
        this.threadId = threadId;
        this.url = url;
        this.start = start + progress;// 1M-2M 0.5M  1.5M - 2M
        this.end = end;
        mCallback = callback;
        this.mProgress = progress;
        this.mDownloadEntity = downloadEntity;
    }

    @Override
    public void run() {
        // 只读写我自己的内容,Range
        RandomAccessFile accessFile = null;
        InputStream inputStream = null;
        try {
            Response response = OkHttpManager.getManager().syncResponse(url,start,end);
            Log.e("TAG",this.toString());

           inputStream = response.body().byteStream();
            // 写数据
            File file = FileManager.manager().getFile(url);
            // 从这里开始
            accessFile.seek(start);

            int len = 0;
            byte[] buffer = new byte[1024*10];

            while ((len = inputStream.read(buffer))!=-1){
                if(mStatus == STATUS_STOP)
                    break;
                // 保存进度,做断点 , 100kb
                mProgress += len;
                accessFile.write(buffer,0,len);
            }

            mCallback.onSucceed(file);
        } catch (IOException e) {
            mCallback.onFailure(e);
        }finally {
            Utils.close(inputStream);
            Utils.close(accessFile);

            // 存到数据库,数据库怎么存?
            mDownloadEntity.setProgress(mProgress);
            DaoManagerHelper.getManager().addEntity(mDownloadEntity);
        }
    }

    @Override
    public String toString() {
        return "DownloadRunnable{" +
                "start=" + start +
                ", end=" + end +
                ", threadId=" + threadId +
                ", url='" + url + '\'' +
                '}';
    }

    public void stop() {
        mStatus = STATUS_STOP;
    }

6 参考文献

  1. hongyang
  2. okhttp的使用教程