Okhttp原理

125 阅读7分钟

1. 整体思路 从使用方法出发,首先是怎么使用,其次是我们使用的功能在内部是如何实现的,实现方案上有什么技巧,有什么范式。

2. 基本用例

2.1 创建OkHttpClient对象

    OkHttpClient client = new OkHttpClient();

2.2 发起HTTP请求 OkHttpClient实现了Call.Factory,负责根据请求创建新的Call。

  • 同步网络请求

    这里我们做了4件事:

    1.检查这个call是否已经被执行了,每个call只能被执行一次,如果想要一个完全一样的call,可以利用call#clone方法进行克隆。

    2.利用client.dispatcher().executed(this)来进行实际执行dispatcher是OkHttpClient.Builder的成员之一,它的文档说它是异步HTTP请求的执行策略,现在看来,同步请求它也有参和。

    3.调用getResponseWithInterceptorChain()函数获取HTTP返回结果,从函数名可以看出,这一步还会进行一系列“拦截”操作。

    4.最后还要通知dispatcher自己已经执行完毕。

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

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

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

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

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

责任链模式在这个Interceptor链条中得到了很好的实践。(它包含了一些命令对象和一系列的处理对象,每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。该模式还描述了往该处理链的末尾添加新的处理对象的方法。)

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

责任链模式在安卓系统中也有比较典型的实践,例如view系统对点击事件(TouchEvent)的处理。

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

  1. 建立连接:ConnectInterceptor

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

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

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

  1. 发送和接收数据:CallServerInterceptor

我们抓住主干部分:

2.1.向服务器发送request header;

2.2 如果有request body,就向服务器发送;

2.3读取response header,先构造一个Response对象;

2.4如果有response body,就在3的基础上加上body构造一个新的Response对象;

这里我们可以看到,核心工作都由HttpCodec对象完成,而HttpCodec实际上利用的是Okio,而Okio实际上还是用的Socket,所以没什么神秘的,只不过一层套一层,层数有点多。

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

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

  • 发起异步网络请求

    如果当前还能执行一个并发请求,那就立即执行,否则加入readyAsyncCalls队列,而正在执行的请求执行完毕之后,就会调用promoteCalls()函数,来把readyAsyncCalls队列中的AsyncCall提升为runningAsyncCalls,并开始执行。

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

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

2.3返回数据的获取

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

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

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

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

这里有一点值得一提,OkHttp对响应的校验非常严格,HTTP status line不能有任何杂乱的数据,否则就会抛出异常。

2.4 HTTP缓存

在建立连接、和服务器通讯之前,就是CacheInterceptor,在建立连接之前,我们检查响应是否已经被缓存、缓存是否可用,如果是则直接返回缓存的数据,否则就进行后面的流程,并在返回之前,把网络的数据写入缓存。

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

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

而如果我们对OkHttp内置的Cache类不满意,我们可以自行实现InternalCache接口,在构造OkHttpClient时进行设置,这样就可以使用我们自定义的缓存策略来。

3. 总结

  • OkHttpClient实现Call.Factory,负责为Request创建Call;
  • Realcall为具体的Call实现,其enqueue()异步接口通过Dispatcher利用ExecuterService实现,而最终进行网络请求时和同步execute()接口一致,都是通过getResponseWithInterceptorChain()函数实现;
  • getResponseWithInterceptorChain()中利用Interceptor链条,分层实现缓存、透明压缩、网络IO等功能。