Android 开源框架 OkHttp 原理剖析

安卓开发领域,很多重要的问题都有了很好的开源解决方案,例如网络请求 OkHttp + Retrofit 简直就是不二之选。“我们不重复造轮子不表示我们不需要知道轮子该怎么造及如何更好的造!,在用了这些好轮子将近两年之后,现在是时候拆开轮子一探究竟了。本文基于 OkHttp 源码对其进行了详细分析。

1,整体思路

从使用方法出发,首先是怎么使用,其次是我们使用的功能在内部是如何实现的,实现方案上有什么技巧,有什么范式。全文基本上是对 OkHttp 源码的一个分析与导读,非常建议大家下载 OkHttp 源码之后,跟着本文,过一遍源码。对于技巧和范式,由于目前我的功力还不到位,分析内容没多少,欢迎大家和我一起讨论。

首先放一张完整流程图(看不懂没关系,慢慢往后看):

2,基本用例

来自 OkHttp 官方网站

2.1,创建 OkHttpClient 对象

  • OkHttpClient client = new OkHttpClient();

咦,怎么不见 builder?莫急,且看其构造函数:

  • public OkHttpClient() {
  • this(new Builder());
  • }

原来是方便我们使用,提供了一个“快捷操作”,全部使用了默认的配置。OkHttpClient.Builder 类成员很多,后面我们再慢慢分析,这里先暂时略过:

  • public Builder() {
  • dispatcher = new Dispatcher();
  • protocols = DEFAULT_PROTOCOLS;
  • connectionSpecs = DEFAULT_CONNECTION_SPECS;
  • 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;
  • }

2.2,发起 HTTP 请求

  • String run(String url) throws IOException {
  • Request request = new Request.Builder()
  • .url(url)
  • .build();
  • Response response = client.newCall(request).execute();
  • return response.body().string();
  • }

OkHttpClient 实现了 Call.Factory,负责根据请求创建新的 Call,在 拆轮子系列:拆 Retrofit中我们曾和它发生过一次短暂的遭遇:

callFactory 负责创建 HTTP 请求,HTTP 请求被抽象为了 okhttp3.Call 类,它表示一个已经准备好,可以随时执行的 HTTP 请求

那我们现在就来看看它是如何创建 Call 的:

  • /**
  • * Prepares the {@code request} to be executed at some point in the future.
  • */
  • @Override public Call newCall(Request request) {
  • return new RealCall(this, request);
  • }

如此看来功劳全在 RealCall 类了,下面我们一边分析同步网络请求的过程,一边了解 RealCall 的具体内容。

2.2.1,同步网络请求

我们首先看 RealCall#execute

  • @Override public Response execute() throws IOException {
  • synchronized (this) {
  • if (executed) throw new IllegalStateException("Already Executed"); // (1)
  • executed = true;
  • }
  • try {
  • client.dispatcher().executed(this); // (2)
  • Response result = getResponseWithInterceptorChain(); // (3)
  • if (result == null) throw new IOException("Canceled");
  • return result;
  • } finally {
  • client.dispatcher().finished(this); // (4)
  • }
  • }

这里我们做了 4 件事:

  1. 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用 call#clone 方法进行克隆。
  2. 利用 client.dispatcher().executed(this) 来进行实际执行,dispatcher 是刚才看到的 OkHttpClient.Builder 的成员之一,它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。
  3. 调用 getResponseWithInterceptorChain() 函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。
  4. 最后还要通知 dispatcher 自己已经执行完毕。

dispatcher 这里我们不过度关注,在同步执行的流程中,涉及到 dispatcher 的内容只不过是告知它我们的执行状态,比如开始执行了(调用 executed),比如执行完毕了(调用 finished),在异步执行流程中它会有更多的参与。

真正发出网络请求,解析返回结果的,还是 getResponseWithInterceptorChain

  • private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
  • interceptors.addAll(client.networkInterceptors());
  • }
  • interceptors.add(new CallServerInterceptor(
  • retryAndFollowUpInterceptor.isForWebSocket()));
  • Interceptor.Chain chain = new RealInterceptorChain(
  • interceptors, null, null, null, 0, originalRequest);
  • return chain.proceed(originalRequest);
  • }

OkHttp 开发者之一介绍 OkHttp 的文章里面,作者讲到:

the whole thing is just a stack of built-in interceptors.

可见 Interceptor 是 OkHttp 最核心的一个东西,不要误以为它只负责拦截请求进行一些额外的处理(例如 cookie),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor,它们再连接成一个 Interceptor.Chain,环环相扣,最终圆满完成一次网络请求。

getResponseWithInterceptorChain 函数我们可以看到,Interceptor.Chain 的分布依次是:

  1. 在配置 OkHttpClient 时设置的 interceptors
  2. 负责失败重试以及重定向的 RetryAndFollowUpInterceptor
  3. 负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的 BridgeInterceptor
  4. 负责读取缓存直接返回、更新缓存的 CacheInterceptor
  5. 负责和服务器建立连接的 ConnectInterceptor
  6. 配置 OkHttpClient 时设置的 networkInterceptors
  7. 负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor

在这里,位置决定了功能,最后一个 Interceptor 一定是负责和服务器实际通讯的,重定向、缓存等一定是在实际通讯之前的。

责任链模式在这个 Interceptor 链条中得到了很好的实践(感谢 Stay 一语道破,自愧弗如)。

它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。

对于把 Request 变成 Response 这件事来说,每个 Interceptor 都可能完成这件事,所以我们循着链条让每个 Interceptor 自行决定能否完成任务以及怎么完成任务(自力更生或者交给下一个 Interceptor)。这样一来,完成网络请求这件事就彻底从 RealCall 类中剥离了出来,简化了各自的责任和逻辑。两个字:优雅!

责任链模式在安卓系统中也有比较典型的实践,例如 view 系统对点击事件(TouchEvent)的处理,具体可以参考Android设计模式源码解析之责任链模式中相关的分析。

回到 OkHttp,在这里我们先简单分析一下 ConnectInterceptorCallServerInterceptor,看看 OkHttp 是怎么进行和服务器的实际通信的。

2.2.1.1,建立连接:ConnectInterceptor
top Created with Sketch.