OKHttp概览

1,总体思路

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

首先放一张完整流程图(看不懂不要紧,慢慢日后看):android

okhttp_full_process

2,基本用例

来自 OkHttp 官方网站git

2.1,建立 OkHttpClient 对象

 

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

 

原来是方便咱们使用,提供了一个“快捷操做”,所有使用了默认的配置。OkHttpClient.Builder类成员不少,后面咱们再慢慢分析,这里先暂时略过:算法

 

2.2,发起 HTTP 请求

 

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

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

那咱们如今就来看看它是如何建立 Call 的:设计模式

 

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

2.2.1,同步网络请求

咱们首先看 RealCall#execute服务器

 

这里咱们作了 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

 

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

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

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

从 getResponseWithInterceptorChain 函数咱们能够看到,Interceptor.Chain 的分布依次是:

okhttp_interceptors

  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,在这里咱们先简单分析一下 ConnectInterceptor 和 CallServerInterceptor,看看 OkHttp 是怎么进行和服务器的实际通讯的。

2.2.1.1,创建链接:ConnectInterceptor
 

实际上创建链接就是建立了一个 HttpCodec 对象,它将在后面的步骤中被使用,那它又是何方神圣呢?它是对 HTTP 协议操做的抽象,有两个实现:Http1Codec 和 Http2Codec,顾名思义,它们分别对应 HTTP/1.1 和 HTTP/2 版本的实现。

在 Http1Codec 中,它利用 Okio 对 Socket 的读写操做进行封装,Okio 之后有机会再进行分析,如今让咱们对它们保持一个简单地认识:它对 java.io 和 java.nio 进行了封装,让咱们更便捷高效的进行 IO 操做。

而建立 HttpCodec 对象的过程涉及到 StreamAllocationRealConnection,代码较长,这里就不展开,这个过程归纳来讲,就是找到一个可用的 RealConnection,再利用 RealConnection的输入输出(BufferedSource 和 BufferedSink)建立 HttpCodec 对象,供后续步骤使用。

2.2.1.2,发送和接收数据:CallServerInterceptor
 

咱们抓住主干部分:

  1. 向服务器发送 request header;
  2. 若是有 request body,就向服务器发送;
  3. 读取 response header,先构造一个 Response 对象;
  4. 若是有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象;

这里咱们能够看到,核心工做都由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio 实际上仍是用的 Socket,因此没什么神秘的,只不过一层套一层,层数有点多。

其实 Interceptor 的设计也是一种分层的思想,每一个 Interceptor 就是一层。为何要套这么多层呢?分层的思想在 TCP/IP 协议中就体现得淋漓尽致,分层简化了每一层的逻辑,每层只须要关注本身的责任(单一原则思想也在此体现),而各层之间经过约定的接口/协议进行合做(面向接口编程思想),共同完成复杂的任务。

简单应该是咱们的终极追求之一,尽管有时为了达成目标不得不复杂,但若是有另外一种更简单的方式,我想应该没有人不肯意替换。

2.2.2,发起异步网络请求

 

这里咱们就能看到 dispatcher 在异步执行时发挥的做用了,若是当前还能执行一个并发请求,那就当即执行,不然加入 readyAsyncCalls 队列,而正在执行的请求执行完毕以后,会调用 promoteCalls() 函数,来把 readyAsyncCalls 队列中的 AsyncCall “提高”为 runningAsyncCalls,并开始执行。

这里的 AsyncCall 是 RealCall 的一个内部类,它实现了 Runnable,因此能够被提交到 ExecutorService 上执行,而它在执行时会调用 getResponseWithInterceptorChain() 函数,并把结果经过 responseCallback 传递给上层使用者。

这样看来,同步请求和异步请求的原理是同样的,都是在 getResponseWithInterceptorChain()函数中经过 Interceptor 链条来实现的网络请求逻辑,而异步则是经过 ExecutorService 实现。

2.3,返回数据的获取

在上述同步(Call#execute() 执行以后)或者异步(Callback#onResponse() 回调中)请求完成以后,咱们就能够从 Response 对象中获取到响应数据了,包括 HTTP status code,status message,response header,response body 等。这里 body 部分最为特殊,由于服务器返回的数据可能很是大,因此必须经过数据流的方式来进行访问(固然也提供了诸如 string() 和 bytes() 这样的方法将流内的数据一次性读取完毕),而响应中其余部分则能够随意获取。

响应 body 被封装到 ResponseBody 类中,该类主要有两点须要注意:

  1. 每一个 body 只能被消费一次,屡次消费会抛出异常;
  2. body 必须被关闭,不然会发生资源泄漏;

在 2.2.1.2,发送和接收数据:CallServerInterceptor 小节中,咱们就看过了 body 相关的代码:

 

由 HttpCodec#openResponseBody 提供具体 HTTP 协议版本的响应 body,而 HttpCodec 则是利用 Okio 实现具体的数据 IO 操做。

这里有一点值得一提,OkHttp 对响应的校验很是严格,HTTP status line 不能有任何杂乱的数据,不然就会抛出异常,在咱们公司项目的实践中,因为服务器的问题,偶尔 status line 会有额外数据,而服务端的问题也毫无头绪,致使咱们不得不忍痛继续使用 HttpUrlConnection,然后者在一些系统上又存在各类其余的问题,例如魅族系统发送 multi-part form 的时候就会出现没有响应的问题。

2.4,HTTP 缓存

在 2.2.1,同步网络请求 小节中,咱们已经看到了 Interceptor 的布局,在创建链接、和服务器通信以前,就是 CacheInterceptor,在创建链接以前,咱们检查响应是否已经被缓存、缓存是否可用,若是是则直接返回缓存的数据,不然就进行后面的流程,并在返回以前,把网络的数据写入缓存。

这块代码比较多,但也很直观,主要涉及 HTTP 协议缓存细节的实现,而具体的缓存逻辑 OkHttp 内置封装了一个 Cache 类,它利用 DiskLruCache,用磁盘上的有限大小空间进行缓存,按照 LRU 算法进行缓存淘汰,这里也再也不展开。

咱们能够在构造 OkHttpClient 时设置 Cache 对象,在其构造函数中咱们能够指定目录和缓存大小:

 

而若是咱们对 OkHttp 内置的 Cache 类不满意,咱们能够自行实现 InternalCache 接口,在构造 OkHttpClient 时进行设置,这样就可使用咱们自定义的缓存策略了。

3,总结

OkHttp 还有不少细节部分没有在本文展开,例如 HTTP2/HTTPS 的支持等,但创建一个清晰的概览很是重要。对总体有了清晰认识以后,细节部分若有须要,再单独深刻将更加容易。

在文章最后咱们再来回顾一下完整的流程图:

okhttp_full_process

  • OkHttpClient 实现 Call.Factory,负责为 Request 建立 Call
  • RealCall 为具体的 Call 实现,其 enqueue() 异步接口经过 Dispatcher 利用 ExecutorService 实现,而最终进行网络请求时和同步 execute() 接口一致,都是经过 getResponseWithInterceptorChain() 函数实现;
  • getResponseWithInterceptorChain() 中利用 Interceptor 链条,分层实现缓存、透明压缩、网络 IO 等功能;
相关文章
相关标签/搜索