原理:主要是通过 5 个拦截器和 3 个队列(同步队列,异步队列,等待队列)工作,内部实现通过一个责任链模式完成,将网络请求的各个阶段封装到各个链条中,实现了各层的解耦。
5个拦截器都是哪些?有什么作用
- RetryAndFollowUpInterceptor(重试和重定向拦截器)
第一个接触到请求,最后接触到响应;负责判断是否需要重新发起整个请求 。重试与重定向拦截器主要处理Response - BridgeInterceptor(桥接拦截器)
补全请求,并对响应进行额外处理,完成应用层和网络层的桥接 - CacheInterceptor(缓存拦截器)
请求前查询缓存,获得响应并判断是否需要缓存 - ConnectInterceptor(链接拦截器)
与服务器完成TCP连接 (Socket),内部维护一个连接池,负责连接复用、创建连接、释放连接 - CallServerInterceptor(请求服务拦截器)
与服务器通信;封装请求数据与解析响应数据(如:HTTP报文)。里面用的okio库,主要是segment的机制运用内存共享和复用,数据不需要进行二次copy,尽可能少的去申请内存,同时也就降低了GC的频率。强了流与流交互,优化缓存策略减小内存压力和性能消耗
RetryAndFollowUpInterceptor (重试和重定向拦截器)
状态码 | 描述 |
---|---|
1xx | 信息,服务器收到请求,需要请求者继续执行操作 |
2xx | 成功,操作被成功接收并处理 |
3xx | 重定向,需要进一步的操作以完成请求 |
4xx | 客户端错误,请求包含语法错误或无法完成请求 |
5xx | 服务器错误,服务器在处理请求的过程中发生了错误 |
RetryAndFollowUpInterceptor 总结
- 是整个责任链中的第一个,是首次接触到Request与最后接收到 Response的,主要功能就是判断是否需要重试与重定向。
- 重试的前提是出现了 RouteException 或IOException 。一旦在后续的拦截器执行过程中出现这两个异常,就会通过 recover 方法进行判断是否进行连接重试。
- 重定向发生在重试的判定之后,如不满足重试的条件,还需要进一步调用 followUpRequest 根据Response 的响应码(如果直接请求失败, Response都不存在就会抛出异常)。 followup 最大发生20次。
BridgeInterceptor(桥接拦截器)
请求头 | 说明 |
---|---|
Content-Type | 请求体类型(如 application/x-www-form-urlencoded) |
Content-Length/Transfer-Encoding | 请求体解析方式 |
Host | 请求的主机站点 |
Connection:Keep-Alive | 默认保持长连接 |
Accept-Encoding:gzip | 接受响应体使用gzip压缩 |
Cookie Cookie | 身份识别 |
User-Agent | 用户信息,如操作系统、浏览器等 |
在补全了请求头后交给下一个拦截器处理,得到响应后,主要干两件事情:
- 如果使用gzip返回的数据,则使用GzipSource包装便于解析。
- 保存cookie,在下次请求则会读取对应的数据设置进入请求头,默认的 CookieJar 不提供实现
BridgeInterceptor总结:
对用户构建的 Request 进行添加或者删除相关头部信息,以转化成能够真正进行网络请求的 Request 将符合网络 请求规范的Request交给下一个拦截器处理,并获取 Response 如果响应体经过了GZIP压缩,那就需要解压,再构 建成用户可用的 Response 并返回
CacheInterceptor (缓存拦截器)
只有当互联网可用时才会返回缓存的响应,因为 OkHttp 就是这样设计的。
在进入 OkHttp Core 之前,我们必须拦截 Response 并添加 header(Cache-Control),所以它会被视为响应(带有 Cache-Control header)已经到来来自服务器,OkHttp Core 会尊重并缓存响应。
创建缓存拦截器
public class CacheInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response respOnse= chain.proceed(request);
String cache = request.header("Cache-Time");
if (!Util.checkNULL(cache)) {//缓存时间非空
Response response1 = response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
//cache for cache seconds
.header("Cache-Control", "max-age="+cache)
.build();
return response1;
} else {
return response;
}
}
}
配置缓存路径
public class CacheFile {
Context mContext;
public CacheFile(Context context) {
mCOntext= context;
}
public Cache provideCache() {//使用应用缓存文件路径,缓存大小为10MB
return new Cache(mContext.getCacheDir(), 10 * 1024 * 1024);
}
}
指定了缓存的大小为10MB。这里如果缓存的数据量大于这个值,内部会使用lur规则进行删除。
使用
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new CacheInterceptor())//缓存拦截器
.cache(new CacheFile(mAppliactionContext).provideCache())//缓存空间提供器
.connectTimeout(8, TimeUnit.SECONDS)
.readTimeout(3, TimeUnit.SECONDS)
.writeTimeout(3, TimeUnit.SECONDS)
.build();
ConnectInterceptor(链接拦截器)
拦截器中的所有实现都是为了获得一份与目标服务器的连接,在这个连接上进行HTTP数据的收发。
CallServerInterceptor(请求服务拦截器)
CallServerInterceptor完成HTTP协议报文的封装和解析。
addInterceptor与addNetworkInterceptor的区别
二者通常的叫法为应用拦截器和网络拦截器,从整个责任链路来看,应用拦截器是最先执行的拦截器,也就是用户自己设置request属性后的原始请求,而网络拦截器位于ConnectInterceptor和CallServerInterceptor之间,此时网络链路已经准备好,只等待发送请求数据。
首先,应用拦截器在RetryAndFollowUpInterceptor和CacheInterceptor之前,所以一旦发生错误重试或者网络重定向,网络拦截器可能执行多次,因为相当于进行了二次请求,但是应用拦截器永远只会触发一次。另外如果在CacheInterceptor中命中了缓存就不需要走网络请求了,因此会存在短路网络拦截器的情况。
其次,如上文提到除了CallServerInterceptor,每个拦截器都应该至少调用一次realChain.proceed方法。实际上在应用拦截器这层可以多次调用proceed方法(本地异常重试)或者不调用proceed方法(中断),但是网络拦截器这层连接已经准备好,可且仅可调用一次proceed方法。
最后,从使用场景看,应用拦截器因为只会调用一次,通常用于统计客户端的网络请求发起情况;而网络拦截器一次调用代表了一定会发起一次网络通信,因此通常可用于统计网络链路上传输的数据。