OkHttp深入分析——源码分析部分

一、前言

《OkHttp深入分析——基础认知部分》对 OkHttp 源码的工程已经有一个大致的了解了,这篇文章里就来分析下核心模块 OkHttp。首先看看代码结构的框架图。

OkHttp.jpg

框架图中可以看到涉及到的东西非常多,也看到了很多协议相关的东西。前面的文章中有分析过 Volley——《Volley还算比较深入的分析》,也分析过 Android 原生的网络框架——《Android原生网络库HttpURLConnection分析——HTTP部分》《Android原生网络库HttpURLConnection分析——HTTPS部分》。通过将这 3 者对比不难发现,OkHttp 其实是一个低级别的网络框架,其和 Android 原生网络库 HttpURLConnection 是一个级别的,属于 Http 协议层。它是完全取代了系统的原生 Http 协议的实现,同时还加上了高度的抽象与封装。而 Volley 只是一个更高度的抽象与封装,其最大的目的在于使我们使用 Http 更简单,对 Http 的通信其实是没有太大实质性的改进的。当然,你可以指定网络库为 OkHttp。

二、源码分析

源码分析以发起一个异步的 Get 请求为主线进行分析,Post 请求以及同步方式请求就忽略了。掌握知识抓主干,其他的遇到问题再来解决问题。

1.Demo

如下的 Demo 是从工程源码 sample 中的 AsynchronousGet 中提出来的。

// 1. 构建 OkHttpClient
OkHttpClient client = new OkHttpClient();
// 2.构建一个 Request
Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();
// 3.使用 client 插入一个异步请求,在 Callback 中等待返回结果
client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        // 4.请求成功后,返回结果会被封装在 Response 中
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }
          System.out.println(responseBody.string());
        }
      }
    });
复制代码

Demo 中的代码很简单,总共分了 4 个步骤,这也是分析 OkHttp 的主线。

2.构建 OkHttpClient

构建 OkHttpClient 的分析是非常重要的,它代表了框架的上下文,为网络请求的运行初始化了一切的环境。先来看一下它的类图。

OkHttpClient.jpg

属性非常多,密密麻麻的。关于每个属性的意思,放在下面的 Builder 中一一讲述。那再来看一下构造方法。

  public OkHttpClient() {
    this(new Builder());
  }
复制代码

默认构建方法 new 了一个默认的 Builder,也就是一切规则都采用默认的。

......
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
......
public Builder() {
      dispatcher = new Dispatcher();
      protocols = DEFAULT_PROTOCOLS;
      connectionSpecs = DEFAULT_CONNECTION_SPECS;
      eventListenerFactory = EventListener.factory(EventListener.NONE);
      proxySelector = ProxySelector.getDefault();
      if (proxySelector == null) {
        proxySelector = new NullProxySelector();
      }
      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;
      callTimeout = 0;
      connectTimeout = 10_000;
      readTimeout = 10_000;
      writeTimeout = 10_000;
      pingInterval = 0;
    }
复制代码

把它们用表格整理下再来看看,好像也还数得清的,共 25 个。

序号 字段名 说明
1 dispatcher 任务分发器
2 protocols 支持的协议集,默认为 Http2 和 Http 1.1
3 connectionSpecs 主要是针对 https 的 socket 连接的配置项,包括在协商安全连接时使用的TLS版本和密码套件。
4 eventListenerFactory 创建 EventListener 的工厂方法类,重点在于 EventListener,通过 EventListener 我们可以得到更多有关 Http 通信的可度量数据 ,如数量、大小和持续时间,以便于我们更加详细的统计网络的状况。
5 proxySelector 代理选择器,默认一般用系统的DefaultProxySelector。
6 cookieJar 定义了如何存储或者读取 cookies,如果不设置则不存储 cookie。
7 Cache 缓存 Response 结果,如果不设置则为 null
8 InternalCache 内部缓存
9 socketFactory SocketFactory,主要就是定义如何创建 socket,默认是 DefaultSocketFactory。
10 hostnameVerifier HostnameVerifier 接口的实现,与证书校验相关。在握手期间,如果通信 URL 的主机名和服务器的标识主机名不匹配或者说不安全时,则底层的握手验证机制会回调 HostnameVerifier 接口的实现程序来确定是否应该允许此连接。
11 certificatePinner 证书锁定,防止证书攻击。典型的用例是防止代理工具抓包。
12 authenticator 授权相关,如著名的 401 返回码。一般场景是在 token 过期的情况下发生,但在实际开发中,大部分服务器不会这样实现,而是正常返回在自定义码里面。
13 proxyAuthenticator 经过代理服务器的授权相关
14 connectionPool 连接池
15 dns DNS,没有设置的话则为系统的 DNS 列表
16 followSslRedirects 是否允许 SSL 重定向
17 followRedirects 允许重定向
18 retryOnConnectionFailure 允许失败重连
19 callTimeout 调度超时时间
20 connectTimeout 连接超时时间
21 readTimeout 读取超时时间
22 writeTimeout 写入超时时间
23 pingInterval ping 的间隔时间
24 interceptors 拦截器
25 networkInterceptors 网络拦截器

这里挑出几个重要的属性作进一步的展开,其他没有展开的看看表格里的说明即可。

2.1 分发调度器 Dispatcher

先来看一看它的类图。

Dispatcher.jpg

  • executorService: 线程池调度器,可以由外部指定。如果没有指定,则有默认值。默认线程池的指定核心线程数为 0,但最大线程数为 Integer.MAX_VALUE,阻塞队列是一个无容量的阻塞队列,以及超出核心线程数的线程的存活时间为 60 s。其特点是如果线程数量大于核心线程数,但小于等于最大线程数,且阻塞队列是 SynchronousQueue 的时候,线程池会创建新线程来执行任务,因为 SynchronousQueue 是没有容量的。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。线程同时是由一个线程工厂创建的,创建出来的线程名字为 "OkHttp Dispatcher"。

  • readyAsyncCalls,runningAsyncCalls,runningSyncCalls: 分别是异步等待队列,异步运行时队列以及同步运行时队列。它们的类型都是 ArrayDeque,该类是双端队列的实现类。

  • idleCallback: 也就是当 Dispatcher 中没有任何任务执行时,也就是进入了 idle 状态了,所执行的 runnable 类型的 Callback。

  • maxRequests 和 maxRequestsPerHost: maxRequests 也就是最大请求数,默认为 64。而 maxRequestsPerHost 指的是以 url 中的域名来判断的最大的 host 的数量,默认为 5。

Dispatcher 就先了解到这里了,后面在分析异步请求的过程中还会讲述它是如何进行调度的。

2.2 缓存 Cache

缓存 Cache 就是将 Response 结果存储在磁盘上,并且配合 Http 协议的 Cache-controller 等规则对缓存结果进行增删改查。也就是过期了就去请求网络结果并更更新缓存或者删除缓存;没过期则就用缓存中的结果而不用去请求网络结果,从而节省时间以及宽带。

2.3 连接池 ConnectionPool

主要是用来管理HTTP和HTTP/2连接的重用,以减少网络延迟。对于共享同一个 Address 的请求,其也会共享同一个连接。连接池里的连接一直保持着连接,以待同一连接的其他请求来复用。

2.4 拦截器 interceptors 与 网络拦截器 networkInterceptors

这 2 个都是拦截器,我们也都可以添加自己的拦截器以达到我们自己的目的。其中 interceptors 也称作 application interceptors,它主要发生在请求前和请求后这 2 个阶段。而 network interceptors 则发生在请求中,即网络实际通信的过程中。官方有一个图来帮助我们理解它们之间的作用以及关系。

image.png

OkHttpClient 的构建就是一个网络通信环境的初始化,细数下来其关键的几个部分是任务分发器,缓存,连接池,拦截器。这里先对它们有一个基础的认知,后面还会再和他们一一碰面的。

3.构建 Request

Request 没有提供默认可用的构造方法给我们,必须用 Request.Builder 来进行构造。其类图结构如下。

Request.jpg

Request 看起来就简单多了。下面就几个重要的属性做一个简要的说明。

3.1 HttpUrl

盗个图吧,能说明一切的图。

HttpUrl

3.2 Headers

Headers 就是存放 Http 协议的头部参数,一般来说是 Key-values 形式。但 Headers 的实际实现不是用 Map 之类的结构,而是用了一个 String 数组来存储,并且规定了偶数为 key,奇数为 value。这样的设计下明显不管是时间上还是空间上都要优于 Map 类的结构。当然,Headers 不仅用于 Request,其还用于 Response。

3.3 RequestBody

RequestBody 其实是用于 Post 请求的,也就是请求的主体内容。 主体内容可以有许多种类,如文件,表单以及Json字串等等。RequestBody 由两部分组成,即 content 以及用于描述 content 类型的 MediaType。

一般情况下,通过 Charles 抓包可以观察到,一个 Get 请求的样子通常如下所示,这是请求 www.baidu.com 的根目录 / 。

Request

4.提交异步请求,等待返回结果

构建好了 OkHttpClient 就有了基础环境,构建好了 Request 知道了我们要发送什么出去。接下来就是提交请求,等待处理。那么提交给谁呢?接下来一步一步分析。

4.1 提交异步 Get 请求

先来过一遍时序图。

提交异步Get请求.jpg

首先通过 OkHttpClient 的 newCall 方法创建一个 RealCall,RealCall 实现自接口 Call。它的类图结构如下。

Call.jpg

newCall 方法比较简单,它只是进一步通过 RealCall 的静态方法 newRealCall 来实际创建一个 RealCall 实例。

  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;
  }
复制代码

这个方法使得 RealCall 对象同时拿到 OkHttpClient,Request 以及 eventListener 3 个重要的引用,并记录在其对象中。

然后下一步调用的就是 RealCall 的 enqueue() 方法,但注意这里的参数是一个 Callback。看看它的实现。

@Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    // 通知连接开始
    eventListener.callStart(this);
    // 向 OkHttpClient 的 Dispatcher 插入一个 AsyncCall
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }
复制代码

enqueue() 方法又进一步调用了 OkHttpClient 的属性 Dispatcher 的 enqueue,并且还 new 了一个 AsyncCall 来作为参数入队。看一看 AsyncCall 的类图结构。

AsyncCall.jpg

AsyncCall 是一个 Runnable,这么说来 AsyncCall 才是 Dispatcher 就实际调度的 Runnable。进一步看看 Dispatcher 的 enqueue 方法。

  void enqueue(AsyncCall call) {
    synchronized (this) {
      readyAsyncCalls.add(call);
    }
    promoteAndExecute();
  }
复制代码

先是将其添加到队列 readyAsyncCalls 中去,然后调用 promoteAndExecute() 方法将 AsyncCall 从 readyAsyncCalls 移到 runningAsyncCalls 中去,并将 AsyncCall 交由 ExecutorService 来进行线程池调度。关于 Dispatcher 以及 ExecutorService 的特点已经在上面讲过了。这里就不再赘述了。而调度实际上是交给 AsyncCall 的 executeOn () 方法来发起的。

void executeOn(ExecutorService executorService) {
      ......
        executorService.execute(this);
        ......
    }
复制代码

向 ExecutorService 提交了 AsyncCall ,下一步就是等待 AsyncCall 被调度到,然后完成进一步的工作,构建 Interceptor Chain。

4.2 构建 Interceptor Chain

同样是先过一下时序图。

构建 Interceptor Chain.jpg

如果 AsyncCall 被调度到的话,那么就会调起它的 run() 方法。而它的 run() 方法又会调用它的 execute() 方法。

@Override protected void execute() {
      .....
      Response response = getResponseWithInterceptorChain();
      ......
}
复制代码

这里调用的是 RealCall 的 getResponseWithInterceptorChain() 方法,getResponseWithInterceptorChain() 方法执行完成后就得到了最终的结果 Response。

看到这里插入一段话,这里总结一下 RealCall 与 AsyncCall 的关系,还真是有一点微妙。通过 OkHttpClient 创建了 RealCall 的时候将 Request 已经给 RealCall 来持有了,然后向 RealCall 的 enqueue 入队时其是一个 Callback。而内部其实又创建了一个 AsyncCall 用来给 OkHttpClient 的 Dispatcher 来进行调度。也就是说 RealCall 才是真正执行任务的,而 AsyncCall 是起到其中的调度作用。被调度起的 AsyncCall 又来驱动 RealCall 来执行任务。

然后再来看看 getResponseWithInterceptorChain() 的实现吧。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    // 用户添加的 interceptor,注意此时请求还未开始。
    interceptors.addAll(client.interceptors());
    // 添加框架内所必须的 ****Interceptor
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      // 非 web socket 的情况下,还会添加用户所添加的 networkInterceptors。
      interceptors.addAll(client.networkInterceptors());
    }
    // 最后添加 CallServerInterceptor,这是 Interceptor Chain 的终止符。什么意思呢?后面还会再讲到。
    interceptors.add(new CallServerInterceptor(forWebSocket));
    // 构建一个起始 RealInterceptorChain
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
        originalRequest, this, eventListener, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());
    // 开始 Interceptor Chain 的执行以获取 Response。
    return chain.proceed(originalRequest);
  }
复制代码

这段构造 Interceptor Chain 的代码看起来多,但其实是非常简单的。就是将所有的 Interceptor 包括用户定义的以及框架所必须的按照顺序组织成一个列表,并结合 RealInterceptorChain 来构造出 Interceptor Chain。这个 Interceptor Chain 实现的确实很精妙,但说实话它又被很多人所神话了,好像其非常难且不可思议一样。我们来进一步看看它的工作原理。

4.3 Interceptor Chain 工作原理

先来看看它的工作原理图

Interceptor Chain 工作原理.jpg

从原理图上可以看出,在 Chain 的 proceed() 方法中调用了 Interceptor 的 intercepter() 方法,而在 Interceptor 的 intercepter() 方法中又调用了 Chain 的 proceed() 方法。这是 2 个方法组成的递归调用,入口是 getResponseWithInterceptorChain() 中所 new 出来的 Chain,而出口是 CallServerInterceptor。

public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
   ......
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);
   ......
    return response;
  }
复制代码

把该方法精简单了一下,核心代码就上面那 3 句。先构造出 next Chain,注意其中的 index + 1,也就是说下一个 Chain 得调用下一个 Interceptor。这里先获取了当前 index 的 Interceptor 的 interceptor 方法。注意这里传递了参数 next Chain,告诉 Interceptor 应该在执行完后就调用 Chain 的 proceed 继续处理,直到遇到 CallServerInterceptor 不再调用 Chain 的 proceed 了,从而终结 Intercepter Chain 的执行。

Intercepter Chain 执行起来了,下面就该获取其结果了。

5.获取 Response

获取 Response 的过程,就是 Intercepter Chain 的执行过程,而进一步精简地说,就是 Interceptor 的执行。这里假设没有用户所添加的 Interceptor,那接下来要分析的就是框架内的那 5 个重要的 Interceptor。也就是 getResponseWithInterceptorChain 中所添加的 RetryAndFollowUpInterceptor,BridgeInterceptor,CacheInterceptor,ConnectInterceptor 以及 CallServerInterceptor。而它们的执行顺序就是它们的添加顺序。

而在分析之前,先来看看这个要获取的 Response 究竟是什么?

5.1 关于 Response

Response.jpg

相对于 Request 来说,Response 显然要内容上要多得多。code,protocol以及message 就是请求行。headers 与 Request 中的 headers 含义上是一样的。handshake 主要是用于握手的。body 就是它的实际内容。

一般情况下,通过 Charles 抓包可获取如下图所示的结果。

Response.png

5.2 RetryAndFollowUpInterceptor

对于每一个 Interceptor 而言,只要从它的 intercept() 方法开始分析就可以了。

@Override public Response intercept(Chain chain) throws IOException {
    // 获取 Request
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    // 获取 RealCall
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    // 创建 StreamAllocation
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;
   // 记录请求的次数,包括正常,重试,重定向以及认证的次数
    int followUpCount = 0;
    Response priorResponse = null;
    // 通过循环体来实现失败后的重试或者重定向等
    while (true) {
      ......
      Response response;
      ......
        response = realChain.proceed(request, streamAllocation, null, null);
        ......
      Request followUp;
      try {
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
        streamAllocation.release();
        return response;
      }
      // 达到次数上限了
      if (++followUpCount > MAX_FOLLOW_UPS) {
        ......
      }
     ......
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(followUp.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
      } 
      request = followUp;
      priorResponse = response;
    }
  }
复制代码

用流程图简化一下上面的逻辑,如下所示。

RetryAndFollowUpInterceptor.jpg

从上面的实现来看,其主要是用了一个循环体来控制,如果请求没有成功就会再次执行 Chain.proceed() 方法,以再次运行起 Interceptor Chain。而循环的次数由 MAX_FOLLOW_UPS 来决定,其默认大小是 20。

而是否成功的检查都是通过 followUpRequest 来进行的。从上面代码也可以看到,如果它返回 null 则说明请求是成功的,否则肯定是出了其他“异常”。

private Request followUpRequest(Response userResponse, Route route) throws IOException {
    ......
    int responseCode = userResponse.code();
    final String method = userResponse.request().method();
    switch (responseCode) {
      case HTTP_PROXY_AUTH:
       ......
        return client.proxyAuthenticator().authenticate(route, userResponse);
      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);
      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        .......
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        ......
        return requestBuilder.url(url).build();
      case HTTP_CLIENT_TIMEOUT:
        ......
        return userResponse.request();
      case HTTP_UNAVAILABLE:
        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_UNAVAILABLE
          return null;
        }
        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          return userResponse.request();
        }
        return null;
      default:
        return null;
    }
  }
复制代码

主要就是通过 response code 进行的一系列异常行为的判断,从而决定是否需要重新运行起 Interceptor Chain 从面达到重连或者修复网络的问题。

5.3 BridgeInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    // 处理 body,如果有的话
    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
    // 处理 host
    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }
    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    boolean transparentGzip = false;
    // 请求以 GZip 来压缩
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    // 处理 Cookies,如果有的话
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    // 处理 UA
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    // 调 proceed() 等待 Response 返回
    Response networkResponse = chain.proceed(requestBuilder.build());

    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);

    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }
复制代码

同样先用流程图来总结一下。

BridgeInterceptor.jpg

BridgeInterceptor 如其名字,桥接器,用于桥接 request 和 reponse。主要就是依据 Http 协议配置请求头,然后通过 chain.proceed() 发出请求。待结果返回后再构建响应结果。而关于上面代码各部分的细节,请参考 Http 协议相关文档以了解每个 Header 的意义。

5.4 CacheInterceptor

@Override public Response intercept(Chain chain) throws IOException {
        // 从 Cache 中取得 Reponse
        Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    // 通过CacheStrategy.Factory构造出CacheStrategy,从而判断 Cache 是否可用
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
   .....
    // 命中 Cache
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    Response networkResponse = null;
    try {
      // 进一步执行网络请求
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
   ......
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
       // 存储 Cache
        return cacheWritingResponse(cacheRequest, response);
      }
    }

    return response;
  }
复制代码

流程图总结如下:

CacheInterceptor.jpg

上面的实现就是先看一看是否命中 cahce,如果命中的话就直接返回,如果没有的话就进一步请求网络。而如果最终有获取的结果,只要条件允许 ,就会将 response 写入到 Cache 中。而是否命中 Cache 的实现都在 CacheStrategy 中。

5.5 ConnectInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
复制代码

其流程图如下

ConnectInterceptor.jpg

其主要目的寻找一个可用的 Connection,并将获取到可用的 httpCodec 以及 connection 推到下一个 Chain 中,以向服务器发起真正的请求。这里的 Connection 具体点其实指的就是 TCP,甚至包括了 TLS 在内的连接。并同时通过 Okio 包装了写入流 BufferSink 与读取流 BufferSource。

关于 Okio,可以去参考《Okio深入分析——基础使用部分》《Okio深入分析—源码分析部分》

5.6 CallServerInterceptor

@Override public Response intercept(Chain chain) throws IOException {
    ......
    // 写入请求头
    httpCodec.writeRequestHeaders(request);
    ......
    HttpSink httpSink = null;
    Response.Builder responseBuilder = null;
    ......
    ......
          // 如果需要写入 request body 的话,则写入
          BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
          request.body().writeTo(bufferedRequestBody);
          .......
    ......
    // 完成请求的写入
    httpCodec.finishRequest();
    .......
   // 获取并构造 response
   responseBuilder = httpCodec.readResponseHeaders(false);
   ......
    responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis());
    Internal.instance.httpSink(responseBuilder, httpSink);
    Response response = responseBuilder.build();
   .......
   .......
    return response;
  }
复制代码

这是整个 Interceptor Chain 中最后一个 Intercepter,它利用在 ConnectInterceptor 建立起的连接以及获取到的写入流,将请求写入到流中,从而实现向服务发送请求调用。

关于这 5 个 Interceptor 都只是点到即止,并没有进行进一步的详细展开。原因是因为这 5 个 Interceptor 恰好是这个库的精华所在,它们其实分别代表了 OkHttp 的基本特性:失败重连,协议支持,缓存,连接重用。每一个特性都该独立成文,同时,从篇幅上来考虑,这里只做大纲性的展开以及结论性的总结。并且,对这些细节不感兴趣的同学看到这里也基本了解了 OkHttp 的整体轮廓了。

三、总结

又到了总结的时候了。这篇文章前后也经历不短的时间,可以说是跨年了。文章先梳理出了 OkHttp 的代码结构,以对库有一个全局的认知。然后以官方 Demo 代码 AsynchronousGet 的请求流程为关键路径来分析请求的整个过程,从而从源码层面了解 OkHttp 的工作原理。

最后,感谢你能读到并读完此文章。受限于作者水平有限,如果分析的过程中存在错误或者疑问都欢迎留言讨论。如果我的分享能够帮助到你,也请记得帮忙点个赞吧,鼓励我继续写下去,谢谢。

猜你喜欢

转载自juejin.im/post/5c2cea1e5188257c30462715