OkHttp
是一个快速、高效的网络请求库,它的设计和实现的首要目标便是高效,有如下特性:
- 支持http2,使得对同一个主机发出的所有请求都可以共享相同的socket套接字连接;
- 使用连接池来复用连接以减少延迟、提高效率;
- 支持Gzip压缩响应体,降低传输内容的大小;
- 支持Http缓存,避免重复请求;
- 请求失败时会自动重试主机中的其他IP地址自动重定向;
- 使用Okio来简化数据的访问与存储,提高性能;
以下通过具体例子来学习一下内部的实现。
|
|
我们会通过OkHttpClient.newCall(request)进行execute或者enqueue操作。
|
|
实际返回的是一个RealCall类,我们调用enqueue异步请求网络实际上是调用了RealCall的enqueue方法:
|
|
最后通过client.dispatcher().enqueue(new AsyncCall(responseCallback))来实现的。
client.dispatcher()返回一个Dispatcher对象。
Dispatcher主要是控制并发的请求,内部维护着一个线程池。
|
|
再来看enqueue方法
|
|
当正在运行的异步请求队列中的数量小于64并且正在运行的请求主机数小于5时则把请求加载runningAsyncCalls中并在线程池中执行,否则就再入到readyAsyncCalls中进行缓存等待。
enqueue方法中传入的AsyncCall是RealCall的内部类。AsyncCall内部实现了execute方法。
|
|
getResponseWithInterceptorChain()方法会返回Response,那这句话应该就是在执行网络请求了。
|
|
这里就使用到了Interceptor拦截器。OKHttp 内部是使用拦截器来完成请求和响应的,利用的是责任链设计模式。所以可以说,拦截器是 OKHttp 的精髓所在。
拦截器主要用来观察,修改以及可能短路的请求输出和响应的回来。通常情况下拦截器用来添加,移除或者转换请求或者响应的头部信息。比如将域名替换为ip地址,将请求头中添加host属性,也可以添加我们应用中的一些公共参数,比如设备id、版本号等等。
说明下各种拦截器:
在配置 OkHttpClient 时设置的 interceptors;
负责失败重试以及重定向的 RetryAndFollowUpInterceptor;
负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应的BridgeInterceptor;
负责读取缓存直接返回、更新缓存的 CacheInterceptor;
负责和服务器建立连接的 ConnectInterceptor;
配置 OkHttpClient 时设置的 networkInterceptors;
负责向服务器发送请求数据、从服务器读取响应数据的 CallServerInterceptor。
其实 Interceptor 的设计也是一种分层的思想,每个 Interceptor 就是一层。为什么要套这么多层呢?分层的思想在 TCP/IP 协议中就体现得淋漓尽致,分层简化了每一层的逻辑,每层只需要关注自己的责任(单一原则思想也在此体现),而各层之间通过约定的接口/协议进行合作,共同完成复杂的任务。
下面来看下缓存拦截器都做了什么
|
|
cacheCandidate是上次与服务器交互缓存的Response,这里的缓存都是基于Map,key是请求中url的md5,value是在文件中查询到的缓存,页面置换基于LRU算法,我们现在只需要知道它是一个可以读取缓存Header的Response即可。根据cacheStrategy的处理得到了networkRequest和cacheResponse这两个值,根据这两个值的数据是否为null来进行进一步的处理,当networkRequest和cacheResponse都为null的情况也就是不进行网络请求并且缓存不存在或者过期,这时候则返回504错误;当networkRequest 为null时也就是不进行网络请求,而且缓存可以使用时则直接返回缓存;其他的情况则请求网络。