1 序
基于OkHttp 3.14.9版本
作为一个Android开发者,不知道OkHttp网络库的几乎是没有了吧。很多时候我们在使用三方库的时候因为时间关系不会去深究其原理,只要会用就行。但是闲下来还是有必要阅读下这些优秀的三方库源码,探究其实现方式和设计模式。阅读优秀的源码,学以致用,是我们快速成长的方式。
2 OkHttp概述
HTTP 是现代应用程序网络的方式。这就是我们交换数据和媒体的方式。有效地执行 HTTP 可以使您的内容加载速度更快并节省带宽。 OkHttp 是一个默认高效的 HTTP 客户端:
- HTTP/2 支持 允许对同一主机的所有请求共享一个套接字。
- 连接池减少了请求延迟(如果 HTTP/2 不可用)。
- transparent GZIP 可缩小下载大小。
- Response缓存完全避免网络重复请求。
- OkHttp在网络故障时会自动重试:它会从常见的连接问题中默默恢复。如果您的服务有多个 IP 地址,则 OkHttp 将在第一次连接失败时尝试备用地址。这对于在冗余数据中心托管的 IPv4+IPv6 和服务是必需的。OkHttp 还支持现代 TLS 功能(TLS 1.3,ALPN,certificate pinning)。It can be configured to fall back for broad connectivity.
以上是官方对于OkHttp的描述,本文会从OkHttp的使用方法出发,通过使用来探究其内部是如何实现的。
3 OkHttp源码分析
3.1 基本用例
使用OkHttp实现GET请求非常简单,例子如下:
1 | |
这个简单的例子,展示了OkHttp的使用过程:
- 创建OkHttpClient对象
- 构造请求Request
- 调用OkHttpClient发送Request
- 解析请求结果
我们将以这个例子为切入点,来分析OkHttp的内部实现:
3.2 创建OkHttpClient对象
OkHttpClient的配置参数太多,使用了建造者模式来创建对象,我们来看下OkHttpClient构造方法:
1 | |
可以看到,OkHttpClient实例化实际上是调用了OkHttpClient(Builder builder),传入了一个OkHttpClient.Builder建造者对象,然后将参数赋值给了自己。
看一看OkHttpClient.Builder的构造方法:
1 | |
内部对很多属性都赋予了默认值,我们根据需要可以通过Builder中各种对应的方法传入自定义的值来替换它们。
3.3 发起请求
构建Request
1 | |
Request类很简单,如上。封装的就是我们发起http请求的各种要素,请求地址,请求方式(GET、POST…),请求头,请求体等。不清楚的可以去看下http协议。
3.3.1 同步请求execute()
如上面示例所示:
1 | |
newCall创建了一个RealCall对象,而RealCall实现了Call接口。Call接口声明如下:
1 | |
可以看到Call接口主要是对请求控制操作,例如调用已准备好执行的请求,取消请求等。Call的实现就是RealCall,我们来看看newCall内部实现:
1 | |
接着来看重点执行同步请求的RealCall.execute方法:
1 | |
我们先来看client.dispatcher().executed(this)方法:
1 | |
方法很简单,就是将call加入到Dispatcher.runningSyncCalls队列中。
接着调用getResponseWithInterceptorChain进行网络请求并获取Response,该方法是OkHttp中的最重要的点,稍后我们在介绍RealCall.enqueue方法时再一起说。
紧接着就是finally代码块里面的client.dispatcher().finished(this):
1 | |
该方法也很简单,就是Call执行完毕后将其从Dispatcher.runningSyncCalls队列中移除,同时还会执行promoteAndExecute方法,此方法是给异步调用准备的,具体代码后面会谈到;最后如果runningAsyncCalls、runningSyncCalls这俩正在执行的同步、异步队列之和为0,说明dispatcher处理空闲状态,那么调用idleCallback.run通知外界dispatcher已经空闲了.
3.3.2 异步请求enqueue()
1 | |
AsyncCall是RealCall中的一个内部类,实现了Runnable接口。先来看下AsyncCall相关代码:
1 | |
可以看到AsyncCall实际上是一个Runnable,执行自己是实际上执行的是内部的execute()方法。
了解了AsyncCall的大致结构后,我们返回Dispatcher.enqueue方法:
1 | |
我们来看下promoteAndExecute()方法实现:
1 | |
我们来看核心的asyncCall.executeOn(executorService())方法,先看executorService():
1 | |
这里我们可以看出来,这是一个典型的CachedThreadPool。是一个线程数量不定的线程池,他只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。线程池中的空闲线程都有超时机制,这个超时时长为60s,超过这个时间的闲置线程就会被回收。SynchronousQueue可以简单的理解为一个无法存储元素的队列,因此这将导致任何任务都会立刻执行。
从其特性来看,这类线程池适合执行大量耗时较少的任务。当整个线程池处于闲置状态时,线程池中的线程都会因为超时而被停止,这个时候CachedThreadPool之中实际上是没有线程的,它几乎不占用任何系统资源。
我们之前在讲AsyncCall结构时说过executeOn方法实际是把自己放入一个线程池执行,最终执行的是AsyncCall.execute方法:
1 | |
可以看到不管是同步请求RealCall.execute还是异步请求RealCall.enqueue最终都会通过调用getResponseWithInterceptorChain()方法去请求并获取结果。我们来看下核心的getResponseWithInterceptorChain方法实现:
1 | |
我们在这儿说一下系统创建的几个拦截器的作用:
- RetryAndFollowUpInterceptor:用于失败重试或者根据需要进行重定向。
- BridgeInterceptor:从应用程序代码到网络代码的桥梁。首先它根据用户请求构建网络请求。然后它继续调用网络。最后它根据网络响应构建用户响应。
- CacheInterceptor:为来自缓存的请求提供服务,并将响应写入缓存
- ConnectInterceptor:与目标服务器的连接
- CallServerInterceptor:这是链中的最后一个拦截器,它对服务器进行网络调用。
用户自定义的interceptor将最先被执行,自定义的networkInterceptor将在与目标服务器建立连接后执行。
可以看到getResponseWithInterceptorChain方法是使用了责任链模式,将所有拦截器依次加入到List中,并创建了一个拦截器责任链RealInterceptorChain链式调用其proceed方法来处理网络请求。这样的好处是这条链中所有的对象都能处理请求,每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则,也避免了请求的发送者和接受者之间的耦合关系。
3.4 责任链模式和拦截器
3.4.1 责任链模式的实现
我们来看一下RealInterceptorChain内部的具体实现:
1 | |
通过上面分析我们知道RealInterceptorChain.proceed和interceptor.intercept会递归调用,执行完所有拦截器的intercept方法,直到遇见最后一个拦截器CallServerInterceptor:
1 | |
可以看到CallServerInterceptor最终返回了相应结果response,而并没有继续调用RealInterceptorChain.proceed方法。这儿就是递归的出口,最终跟服务器交互拿到了response并返回。
通过拦截链的调用我们可以知道:
Request时拦截器是从第一个开始往后执行,Response返回时,拦截器是从最后一个往前执行。如下图:

3.4.2 拦截器
接下来我们来看看通过责任链调用的一个个拦截器中都实现了什么吧:
- RetryAndFollowUpInterceptor:
1 | |
- BridgeInterceptor:
1 | |
上面代码总体来说干了两件事:
- 对原始的
Request进行检查,设置Content-Type、Content-Length、Transfer-Encoding、Host、Connection、Accept-Encoding、Cookie、User-Agent等header - 若是gzip编码,则对响应进行Gzip处理,否则直接返回。
- CacheInterceptor:
首先需要注意的是,OkHttp中的Cache策略采用的是DiskLruCache,关于DiskLruCache可以参考DiskLruCache。key的计算为:
ByteString.encodeUtf8(url.toString()).md5().hex()下面是CacheInterceptor的主要代码:
1 | |
- ConnectInterceptor:
1 | |
上面代码只有几行,作用就是与服务器建立连接,然后传递到下一个拦截器。newExchange 方法中会先通过 ExchangeFinder 尝试去 RealConnectionPool 中寻找已存在的连接,未找到则会重新创建一个 RealConnection 并开始连接,然后将其存入 RealConnectionPool,此时已经准备好了 RealConnection 对象,然后通过请求协议创建不同的 ExchangeCodec 并返回。
通过上面面步骤创建好 ExchangeCodec 之后,再根据它以及其他参数创建 Exchange 对象并返回。ConnectInterceptor 将 Exchange 对象作为参数,调用 Chain.process 方法传递到下一个连接器。
这儿有两个关键的类:
- Transmitter:Transmitter 是 OkHttp 网络层的桥梁,对外提供功能实现。
- Exchange:Exchange 与 Request 一一对应,新建一个请求时就会创建一个 Exchange,该 Exchange 负责将这个请求发送出去并读取到响应数据。Exchange内部有一个ExchangeCodec,它负责对 Request 编码及解码 Response,也就是写入请求及读取响应,我们的请求及响应数据都通过它来读写。
- CallServerInterceptor:
CallServerInterceptor 负责读写数据。这是最后一个 interceptor 了,到了这里该准备的都准备好了,通过它,将会把 Request 中的数据发送到服务端,并获取到数据写入 Response。
1 | |
上面代码很简单,主要操作都放在了Exchange对象中,内部通过okio库来进行了网络的io操作,这里不做过多介绍。
4 总结
OkHttp 还有很多细节部分没有在本文展开,例如 HTTP2/HTTPS的支持,底层网络io 实现等,但建立一个清晰的概览非常重要。对整体有了清晰认识之后,细节部分如有需要,再单独深入将更加容易。
下面贴一张完整的流程图:
