Android源码解读——OkHttp开源框架

OkHttp介绍

由Square公司贡献的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架。从Android4.4开始HttpURLConnection的底层实现采用的是OkHttp。
  • 支持HTTP/2并允许对同一主机的所有请求共享一个套接字
  • 通过连接池,减少了请求延迟
  • 默认通过GZip压缩数据
  • 响应缓存,避免了重复请求的网络
  • 请求失败自动重试主机的其他ip,自动重定向

 

使用方法(get/post)

 

 

 

调用流程

OkHttp请求过程中最少只需要接触OkHttpClient、Request、Call、Response,但是框架内部进行大量的逻辑处理。
所有的逻辑大部分集中在拦截器中,但是在进入拦截器之前还需要依靠分发器来调配请求任务。
  • 分发器:内部维护队列与线程池,完成请求调配;
  • 拦截器:五大默认拦截器完成整个请求过程。
结合上图及上面的使用方法,大致总结为:
  1. 创建OkHttpClient(可以new,也可以通过build构建)
  2. 创建一个Request请求
  3. 将Request请求打包成一个Call(任务)
  4. 选择请求方式(同步 or 异步)
  5. 通过OkHttpClient的dispatcher对Call进行分发
  6. Interceptors进行拦截
  7. 完成请求,接收Response数据

 

分发器:异步请求工作流程(Call+Dispatcher)

流程图如下:

 

结合源码具体分析,由Call开始。当我们将Request打包成Call的时候,实际上是OkHttp内部直接new了一个RealCall 然后返回Call(RealCall实现了Call接口)。

static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }

因此我们得到的call实际上就是RealCall,所以在我们要完成同步请求(execute)或者异步请求(enqueue)时,直接就在RealCall身上去调用execute()方法或者enqueue()方法。

异步请求RealCall.enqueue

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

 

  • 首先该方法需要我们提供一个callback,这个callback就是通知我们请求成功或者失败的一个接口
  • 然后会进过一个if判断,这个if判断表示的是,如果call调用过了execute()方法或者enqueue()方法,那么再次调用的时候回直接报异常
  • 接下来是内部的一些回调和我们自己可以注册的一些监听
  • 最重要的是最后一行代码(client即OkHttpClient):调用OkHttpClient的dispatcher的enqueue方法,把Call打包成一个任务传进去

这个方法就是进行任务分发,看任务是分发到running队列还是ready队列,running队列就是执行队列,ready队列就是等待执行队列

synchronized void enqueue(AsyncCall call) {

  /* 判断执行队列中的任务是否超过64个 */          /* 判断执行队列中的指向同一域名的任务是否超过5个 */
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {

    /* 将任务添加至running队列 */ runningAsyncCalls.add(call);

    /* 分配线程池,执行任务 */ executorService().execute(call); }
else {

    /* 将任务分配至ready队列 */ readyAsyncCalls.add(call); } }

64与5这两个数是OkHttp规定的,意思是:例如我们APP同时访问百度、阿里、腾讯等N个不同域名服务器,最多同时执行的任务不能超过64个,否则手机可能吃不消,这是为了我们手机性能所考虑的,而当我们的running列表访问百度的任务已经到达5个的时候,再来执行访问百度的任务,即使总任务没超过64个,OkHttp也会将该任务放入ready队列而不是放进running队列,目的就是防止服务器过载,试想如果没有这个限定,每台手机都可以同时访问服务器100次,那100台手机是多少?1000呢?

执行任务

executorService().execute(call); call是 AsyncCall 而 AsyncCall 继承自NamedRunnable类,在NamedRunnable类实现了Runnable接口,并重写了run方法,而run方法里面执行的是一个抽象函数execute,所以执行任务的代码实际上就是在AsyncCall.execute()方法里面

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

这段代码是try...catch...finally 所以finally里面的代码必定会执行,因此我们可以知道client.dispatcher().finished(this);必定会执行

/** Used by {@code AsyncCall#run} to signal completion. */
  void finished(AsyncCall call) {
    finished(runningAsyncCalls, call, true);
  }

  /** Used by {@code Call#execute} to signal completion. */
  void finished(RealCall call) {
    finished(runningSyncCalls, call, false);
  }

  private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
    int runningCallsCount;
    Runnable idleCallback;
    synchronized (this) {
      if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
      if (promoteCalls) promoteCalls();
      runningCallsCount = runningCallsCount();
      idleCallback = this.idleCallback;
    }

    if (runningCallsCount == 0 && idleCallback != null) {
      idleCallback.run();
    }
  }

finished方法进行了重载,我们在finally里面调用的是第一个finished方法,实际上执行的是第三个finished方法,切带了两个参数runningAsyncCalls和true

runningAsyncCalls表示running队列,首先会将传过来的call,也就是刚刚执行的任务从队列里面remove。因此可以得到不管访问服务器成功与否,都会执行finally,那么也必然会把任务从running列表里面删除

其次promoteCalls传过来的是true,所以会执行promoteCalls()方法,这个方法就是把任务从ready列表提取到running列表

private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

看见了几个熟悉的判断没?首先会判断是否超过64,其次判断ready队列是否为空,在遍历里面判断同一个域名任务是否超过5个,最后把任务从ready列表提取到running列表,分配线程执行

  • Q1:为什么还要判断是否大于64?在进入这个方法之前不是已经remove掉了刚刚执行完的任务吗?那么最多也是63个,为什么还需要判断?
  • A1:因为在执行完任务remove之后,很有可能有新任务会参与进来,当新任务参与进来的时候首先会判断running队列是否超过64,那么此时running队列在remove了一个任务之后是63,此时新任务会直接进入running队列,而不会进入ready队列,那么此时的running队列达到64个任务,已满。如果这里我们不进行判断直接添加进去,那么就会变成65个任务执行了。
  • Q2:为什么需要遍历?直接取一个任务出来不行吗?
  • A2:遍历的目的是为了判断该任务的访问域名,只要是用于判断同一域名下不能超过5个限制。
  • Q3:是优先级队列吗?
  • A3:不是。是双端队列,不过似乎用不到双端队列的特性。

 同步请求RealCall.execute()

@Override public Response execute() throws IOException {
    ...
  try { client.dispatcher().executed(this); Response result = getResponseWithInterceptorChain(); if (result == null) throw new IOException("Canceled"); return result; } catch (IOException e) { eventListener.callFailed(this, e); throw e; } finally { client.dispatcher().finished(this); } }

通过代码client.dispatcher().executed(this);执行到executed方法。

synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

可以看到这个方法没有做其他的事情,只是把任务添加到了一个队列,这个队列并没有类似异步请求的64和5的限制,这个队列目的是为了方便我们做统计。

因此整个OkHttp的任务队列可以分为三个:

  1. ready队列,异步等待执行的任务队列
  2. runningAsync队列,异步正在执行的任务队列
  3. runningSync队列,同步任务队列

而Response result = getResponseWithInterceptorChain(); 则是真正执行同步请求的代码。

可以看到这里并没有用到线程池,也没有开启新线程去执行任务,因此这里用的是同步请求的方式。

 

线程池

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

在OkHttp里new了一个线程池,这个线程池的特性是具有最大吞吐量、高并发的一个线程池,具体原因可以去参考我的一篇关于线程池的文章。

 

 拦截器(Interceptor)

getResponseWithInterceptorChain()方法
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 自定义拦截器
    interceptors.addAll(client.interceptors());
    // 重试拦截器在交出(交给下一个拦截器)之前,负责判断用户是否取消了请求;在获得了结果之后 ,会根据响应码判断是否需要重定向,如果满足条件那么就会重启执行所有拦截器
    interceptors.add(retryAndFollowUpInterceptor);
    // 桥接拦截器在交出之前,负责将HTTP协议必备的请求头加入其中(如:Host)并添加一些默认的 行为(如:GZIP压缩);在获得了结果后,调用保存cookie接口并解析GZIP数据。
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    // 缓存拦截器,交出之前读取并判断是否使用缓存;获得结果后判断是否缓存。
    interceptors.add(new CacheInterceptor(client.internalCache()));
    // 连接拦截器在交出之前,负责找到或者新建一个连接,并获得对应的socket流;在获得结果后 不进行额外的处理。
    interceptors.add(new ConnectInterceptor(client));
    // 如果不是websocket请求,自定义的拦截器
    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);
  }    

 

posted @ 2020-04-03 11:52  金大人的梦  阅读(189)  评论(0编辑  收藏  举报