1、整体思路
首先放一张完整流程图(看不懂没关系,慢慢往后看):
2、流程分析
基本用法来自 OkHttp 官方网站
1 | OkHttpClient client = new OkHttpClient(); |
2.1 创建OkHttpClient对象
首先是创建OkHttpClient对象,支持两种构造方式
默认方式
1 | public OkHttpClient() { |
这种方式不需要传入任何参数,也就是说基本参数都是默认的,调用的是如下构造函数
1 | public OkHttpClient(Builder builder) {...} |
builder模式
1 | public OkHttpClient build() { |
由此我们可以看到okhttp中的builder设计模式
2.2 创建Request对象
构建完OkHttpClient后就需要构建一个Request对象,查看Request的源码你会发现,你找不到public的构造函数,唯一的一个构造函数是这样的
1 | private Request(Builder builder) { |
这就意味着Request对象是通过builder模式创建的
2.3 发起 HTTP 请求
构建完Request后,我们就需要构建一个Call,一般都是这样的Call call = mOkHttpClient.newCall(request); 那么我们就返回OkHttpClient的源码看看。
1 | public Call newCall(Request request) { |
可以看到newCall方法是Override的,我们再看看OkHttpClient是否有继承和实现
1 | public class OkHttpClient implements Cloneable, Call.Factory { ... } |
我们发现OkHttpClient
实现了 Call.Factory
,负责根据请求创建新的 Call
,这里其实用到了工厂模式的思想,将构建的细节交给具体实现,顶层只需要拿到Call对象即可。
另外我们还可以看到newCall返回的是一个RealCall对象,下面我们就一边分析网络请求的过程,一边看看RealCall的具体内容
2.3.1 同步网络请求
我们首先看 RealCall#execute
:
1 | public Response execute() throws IOException { |
这里我们做了 4 件事:
- 检查这个 call 是否已经被执行了,每个 call 只能被执行一次,如果想要一个完全一样的 call,可以利用
call#clone
方法进行克隆。 - 利用
client.dispatcher().executed(this)
来进行实际执行,dispatcher
是刚才看到的OkHttpClient.Builder
的成员之一,它的文档说自己是异步 HTTP 请求的执行策略,现在看来,同步请求它也有掺和。 - 调用
getResponseWithInterceptorChain()
函数获取 HTTP 返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。 - 最后还要通知
dispatcher
自己已经执行完毕。
dispatcher 用三个队列保存call,分别是runningSyncCalls, runningAsyncCalls, readyAsyncCalls,在同步执行的流程中,涉及到 dispatcher 的内容只不过是告知它我们的执行状态,比如开始执行了(调用 executed
),比如执行完毕了(调用 finished
),也就是加入队列,移除队列的过程。
真正发出网络请求,解析返回结果的,还是 getResponseWithInterceptorChain
:
1 | private Response getResponseWithInterceptorChain() throws IOException { |
在 OkHttp 开发者之一介绍 OkHttp 的文章里面作者讲到:
the whole thing is just a stack of built-in interceptors.
可见 Interceptor
是 OkHttp 最核心的一个东西,不要误以为它只负责拦截请求进行一些额外的处理(例如 cookie),实际上它把实际的网络请求、缓存、透明压缩等功能都统一了起来,每一个功能都只是一个 Interceptor
,它们再连接成一个 Interceptor.Chain
,环环相扣,最终圆满完成一次网络请求。
从 getResponseWithInterceptorChain
函数我们可以看到,Interceptor.Chain
的分布依次是:
- 在配置
OkHttpClient
时设置的interceptors
; - 负责失败重试以及重定向的
RetryAndFollowUpInterceptor
; - 封装Request和Reponse过滤器
BridgeInterceptor
; - 负责读取缓存直接返回、更新缓存的
CacheInterceptor
; - 负责和服务器建立连接的
ConnectInterceptor
; - 配置
OkHttpClient
时设置的networkInterceptors
; - 负责向服务器发送请求数据、从服务器读取响应数据的
CallServerInterceptor
。为了便于理解各个拦截器的作用,这里盗用一张图说明下:
添加完过滤器后,就是执行过滤器了,这里也很重要,一开始看比较难以理解。
1 | Interceptor.Chain chain = new RealInterceptorChain( |
可以看到这里创建了一个RealInterceptorChain,并调用了proceed方法,这里注意一下0这个参数。我们来看看proceed方法的实现
1 | public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream, |
一脸懵逼啊,不要慌,稍微处理下
1 | public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream, |
这样就很清晰了,这里index就是我们刚才的0,也就是从0开始,如果index超过了过滤器的个数抛出异常,后面会再new一个RealInterceptorChain,而且会将参数传递,并且index+1了,接着获取index的interceptor,并调用intercept方法,传入新new的next对象,这里可能就有点感觉了,这里用了递归的思想来完成遍历,为了验证我们的想法,随便找一个interceptor,看一下intercept方法。
1 | public final class ConnectInterceptor implements Interceptor { |
可以看到这里我们拿了一个ConnectInterceptor的源码,这里得到chain后,进行相应的处理后,继续调用proceed方法,那么接着刚才的逻辑,index+1,获取下一个interceptor,重复操作,所以现在就很清楚了,这里利用递归循环,也就是okHttp最经典的责任链模式。
责任链模式在安卓系统中也有比较典型的实践,例如 view 系统对点击事件(TouchEvent)的处理,具体可以参考Android设计模式源码解析之责任链模式中相关的分析。
2.3.2,发起异步网络请求
1 | client.newCall(request).enqueue(new Callback() { |
这里我们就能看到 dispatcher 在异步执行时发挥的作用了,如果当前还能执行一个并发请求,那就立即执行,否则加入 readyAsyncCalls
队列,而正在执行的请求执行完毕之后,会调用 promoteCalls()
函数,来把 readyAsyncCalls
队列中的 AsyncCall
“提升”为 runningAsyncCalls
,并开始执行。
这里的 AsyncCall
是 RealCall
的一个内部类,它实现了 Runnable
,所以可以被提交到 ExecutorService
上执行,而它在执行时会调用 getResponseWithInterceptorChain()
函数,并把结果通过 responseCallback
传递给上层使用者。代码如下:
1 | //RealCall的内部类 |
这样看来,同步请求和异步请求的原理是一样的,都是在 getResponseWithInterceptorChain()
函数中通过 Interceptor
链条来实现的网络请求逻辑,而异步则是通过 ExecutorService
实现。
2.4 返回数据的获取
在上述同步(Call#execute()
执行之后)或者异步(Callback#onResponse()
回调中)请求完成之后,我们就可以从 Response
对象中获取到响应数据了,包括 HTTP status code,status message,response header,response body 等。这里 body 部分最为特殊,因为服务器返回的数据可能非常大,所以必须通过数据流的方式来进行访问(当然也提供了诸如 string()
和 bytes()
这样的方法将流内的数据一次性读取完毕),而响应中其他部分则可以随意获取。
响应 body 被封装到 ResponseBody
类中,该类主要有两点需要注意:
- 每个 body 只能被消费一次,多次消费会抛出异常;
- body 必须被关闭,否则会发生资源泄漏;
这里有一点值得一提,OkHttp 对响应的校验非常严格,HTTP status line 不能有任何杂乱的数据,否则就会抛出异。
2.5 HTTP 缓存
在 [2.3.1,同步网络请求] 小节中,我们已经看到了 Interceptor
的布局,在建立连接、和服务器通讯之前,就是 CacheInterceptor
,在建立连接之前,我们检查响应是否已经被缓存、缓存是否可用,如果是则直接返回缓存的数据,否则就进行后面的流程,并在返回之前,把网络的数据写入缓存。
这块代码比较多,但也很直观,主要涉及 HTTP 协议缓存细节的实现,而具体的缓存逻辑 OkHttp 内置封装了一个 Cache
类,它利用 DiskLruCache
,用磁盘上的有限大小空间进行缓存,按照 LRU 算法进行缓存淘汰,这里也不再展开。
我们可以在构造 OkHttpClient
时设置 Cache
对象,在其构造函数中我们可以指定目录和缓存大小:
1 | public Cache(File directory, long maxSize); |
而如果我们对 OkHttp 内置的 Cache
类不满意,我们可以自行实现 InternalCache
接口,在构造 OkHttpClient
时进行设置,这样就可以使用我们自定义的缓存策略了。
3 总结
在文章最后我们再来回顾一下完整的流程图:
OkHttpClient
实现Call.Factory
,负责为Request
创建Call
;RealCall
为具体的Call
实现,其enqueue()
异步接口通过Dispatcher
利用ExecutorService
实现,而最终进行网络请求时和同步execute()
接口一致,都是通过getResponseWithInterceptorChain()
函数实现;getResponseWithInterceptorChain()
中利用Interceptor
链条,分层实现缓存、透明压缩、网络 IO 等功能;