目录
系列文章:
OKHttp开源框架学习三:任务调度核心类Dispatcher
OKHttp开源框架学习五:拦截器之RetryAndFollowUpInterceptor
OKHttp开源框架学习六:拦截器之BridgeInterceptor
OKHttp开源框架学习八:拦截器之CacheInterceptor
OKHttp开源框架学习九:拦截器之ConnectInterceptor
OKHttp开源框架学习十:ConnectionPool连接池
OKHttp开源框架学习十一:拦截器之CallServerInterceptor
参考文章:
Okhttp拦截器:
官网:拦截器是Okhttp中提供的一种强大机制,它可以实现网络监听、请求、以及响应重写、请求失败重试等功能。
拦截器在哪儿?
这是Okhttp框架提供给我们的拦截器,分为两种,一种是Application拦截器,另一种是Network拦截器。而我们需要关注的就是位于图中绿色的Okhttp core,拦截器就在这里面。
下图是Okhttp core的内部:
可以看到,是一个拦截器链。
源码分析:
拦截器是不区分同步和异步请求的,我们以同步请求为例:
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain(); //位置 1
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
这个是不是很熟悉,我们主要来看位置1:
Response result = getResponseWithInterceptorChain();
点进去:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加用户自定义拦截器
interceptors.addAll(client.interceptors());
//重试和失败重定向拦截器
interceptors.add(retryAndFollowUpInterceptor);
//桥接拦截器,处理一些必须的请求头信息的拦截器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//缓存拦截器
interceptors.add(new CacheInterceptor(client.internalCache()));
//连接拦截器:建立可用的连接,是CallServerInterceptor的基本
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//将http请求写进io流当中,并且从io流中读取response
interceptors.add(new CallServerInterceptor(forWebSocket));
//初始化 RealInterceptorChain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
看到没,这么多的intercepter!!
源码中前面部分主要提到了五个拦截器,分别是:
- RetryAndFollowUpInterceptor:重试和失败重定向拦截器
- BridgeInterceptor:桥接拦截器,处理一些必须的请求头信息的拦截器
- CacheInterceptor:缓存拦截器,用于处理缓存
- ConnectInterceptor:连接拦截器,建立可用的连接,是CallServerInterceptor的基本
- CallServerInterceptor:请求服务器拦截器将 http 请求写进 IO 流当中,并且从 IO 流中读取响应 Response
后半部分很重要了,先是初始化 RealInterceptorChain,然后调用 proceed() 方法,我们跟踪这个方法,进入到 RealInterceptorChain 的源码中,最终定位到 RealInterceptorChain 中的 proceed() 方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//......省略部分代码,只显示核心代码
// Call the next interceptor in the chain.
//调用拦截器链中的下一个拦截器,注意 index + 1 参数
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
//......省略部分代码,只显示核心代码
return response;
}
从 RealInterceptorChain 的 proceed() 方法的核心代码中,我们可以看到。在这个方法中,主要就是新建了一个新的 RealInterceptorChain,只是在第五个 index 参数中,传入了 index + 1,确保调用到的是下一个拦截器,这也是 OKhttp 中拦截器链的关键所在。然后获取当前的拦截器,调用 Interceptor 的 intercept() 方法。我们可以查看上面五个拦截器的 intercept() 方法,可以发现,这五个拦截器的 intercept() 方法中,无一例外都调用了 chain.proceed() 方法,然后 proceed() 方法又会去调用下一个拦截器的 intercept()。Response也是由这条链依次向上返回。由此就形成了一个完整的链条。
从这里我们可以总结得到:拦截器通过 intercept() 调用拦截器链的 proceed() 方法,由此创建下一个拦截器链,然后又调用下一个拦截器的 intercept() 方法。从而形成一个调用链,依次获取 Response 返回。看下面的调用流程图
每个拦截器的作用:
Application Interceptor:
我们可以自定义设置 Okhttp 的拦截器之一。
从流程图中我们可以看到一次网络请求它只会执行一次拦截,而且它是第一个触发拦截的,这里拦截到的url请求的信息都是最原始的信息。所以我们可以在该拦截器中添加一些我们请求中需要的通用信息,打印一些我们需要的日志。
当然我们可以定义多个这样的拦截器,一个处理 header 信息,一个处理 接口请求的 加解密 。
NetwrokInterceptor:
NetwrokInterceptor 也是我们可以自定义的拦截器之一。
它位于倒数第二层,会经过 RetryAndFollowIntercptor 进行重定向并且也会通过 BridgeInterceptor 进行 request请求头和 响应 resposne 的处理,因此这里可以得到的是更多的信息。在打印结果可以看到它内部重定向操作和失败重试,这里会有比 Application Interceptor 更多的日志。
RetryAndFollowInterceptor:
RetryAndFollowUpInterceptor
的作用,看到该拦截器的名称就知道,它就是一个负责失败重连的拦截器。它是 Okhttp
内置的第一个拦截器,通过 while (true)
的死循环来进行对异常结果或者响应结果判断是否要进行重新请求。
.
@Override public Response intercept(Chain chain) throws IOException {
//......
//用于服务端数据传输的输入输出流,依次传到 ConnectInterceptor 用于 http 请求,本 intercepter 没有用到,核心点!!
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
//......
while (true) {
Response response;
boolean releaseConnection = true;
try {
//调用拦截器链,执行下一个拦截器
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
//......
releaseConnection = false;
continue;
} catch (IOException e) {
//......
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
//......
//对重试次数进行判断,当重试次数大于20次时,释放掉streamAllocation
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//......
}
}
这里只展示了部分核心代码,详细代码可查看源码。
前面我们提到,OKHttp 请求是通过 RealInterceptorChain 串联起所有的拦截器,然后依次往回返回 Response,只有所有拦截器正常返回,我们的请求才算成功。但是我们知道,网络在请求过程中是不稳定的,可能会存在不同程度的问题。这个时候 Response 返回码就不一定是200了,Response 也就不能用了。那我们这个拦截器就对这种情况进行了精密的部署,具体的逻辑在代码段的 while 循环中。我们只讲一下两处注释的地方。第一个注释位置:很明显,这里通过调用 chain.proceed() 方法去调用下一个拦截器,并获取 Response;第二次注释位置:这里对重试次数做了限制,避免无限制的重试,能跳出整个逻辑。
另外这段源码中,还创建了一个很重要的内容,那就是
StreamAllocation,它用于收集服务端数据传输时的输入输出流,在本拦截器中创建,但是会依次传到 ConnectInterceptor 用于 http 请求。
RetryAndFollowUpIntercepter 总结:
1. 创建StreamAllocation对象
2. 调用RealInterceptorChain.proceed(…)进行网络请求
3. 根据异常结果或者响应结果判断是否要进行重新请求
4. 调用下一个拦截器,对 Response 进行处理,返回给上一个拦截器
BridgeInterceptor:
BridgeInterceptor 拦截器主要负责对头部信息进行添加(长度、编码方式、压缩等),看一下源码
//省略部分源码
//调用拦截器的 proceed() 方法,请求服务器,获取 Response。
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
//判断 Response 是否进行了 Gzip 压缩,是则对 body 体进行解压。
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
}
前面我省略了一段源码,其实这一段主要就是对请求头进行了处理,大家可以去源码查看。我这里贴上的主要就是两段,一段就是前面一直提到的,调用拦截器的 proceed() 方法,执行下一个拦截器,获取 Response。另外一段就是判断 Response 是否进行了 Gzip 压缩,是则对 body 体进行解压。
BridgeInterceptor 总结:
1. 负责将用户构建的一个 Request 请求转化为能够进行网络访问的请求
2. 将这个符合网络请求的 Request 进行网络请求
3. 将网络请求回来的响应 Response 转化为用户可用的 Response
CacheInterceptor:
CacheInterceptor
根据 OkHttpClient
对象的配置以及缓存策略对请求值进行缓存。
首先我们在解析缓存拦截器源码前,我们先了解一下 OKHttp 缓存的使用方法。
public void cacheRequest() {
//设置缓存目录、大小
OkHttpClient client = new OkHttpClient.Builder()
.cache(new Cache(new File("cache"), 24 * 1024 * 1024)).build();
Request request = new Request.Builder().url("http://www.baidu.com").build();
Call call = client.newCall(request);
try {
call.execute();
} catch (IOException e) {
e.printStackTrace();
}
}
这里我们看到了一个新建的 Cache 类,传入缓存目录和缓存大小参数。跟踪进去查看源码发现,这个 Cache 类其实本质就是 DiskLruCache,也就是说,OKHttp 的缓存策略其实就是用 DiskLruCache 实现的。在分析 CacheInterceptor 前,我们先主要讲一下这个 Cache 类,主要就是 Cache 类的 put() 和 get() 两个方法。
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
//判断是否是无效的缓存
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
//是无效缓存则移除缓存
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//不缓存非 GET 方法的响应
if (!requestMethod.equals("GET")) {
// Don't cache non-GET responses. We're technically allowed to cache
// HEAD requests and some POST requests, but the complexity of doing
// so is high and the benefit is low.
return null;
}
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
// Entry 类:封装了各种属性,用于数据缓存
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
//先把 URL 经 MD5 加密,获取十六进制字符串,然后根据这个字符串创建 Editor
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
//把 Request 与 Response 的头部写入缓存
entry.writeTo(editor);
//返回 CacheRequestImpl,CacheRequestImpl 主要用于缓存 body 信息
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
put 方法总结:
- 判断是否是有效的缓存
- 判断是否是 GET 请求。注意:源码注释中提到,对非 GET 请求技术上能做到缓存,但是复杂性比较高,而且取得的收益很小,所以缓存只针对 GET 请求
- 使用 DiskLruCache.Editor 创建用于数据缓存的 Entry 类,缓存Request 和 Response 的头部信息,然后返回 CacheRequest 的实现类,用于缓存 body 信息。具体 body 缓存的实现位于 CacheIntercepor 中。
@Nullable Response get(Request request) {
//对 URL 进行MD5加密,获取加密后的 16进制字符串
String key = key(request.url());
//缓存快照
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
//根据 key 值从缓存获取缓存快照
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
//赋值 Entry 对象
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//从 Entry 中获取 Response
Response response = entry.response(snapshot);
//匹配 Request 和 Response,如果不匹配,返回 null
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
get方法总结:
根据加密的 key 值,从缓存中获取缓存快照
创建 Entry 对象,从 Entry 中获取 Response
检测 Request 和 Response 是否匹配,不匹配返回 null,匹配返回 Response
在了解了 Cache 类的存、取方法后,我们再来分析 CacheInterceptor。先贴上源码:
@Override public Response intercept(Chain chain) throws IOException {
//获取缓存,可能为空
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//获取缓存策略类,CacheStrategy 内部维护着一个网络请求 Request 和一个缓存 Response,目的是:判断是使用网络还是缓存,还是两者都用
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//......
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
//当 networkRequest 和 cacheResponse 都为空时,构建一个错误码为504的 Response
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.当有缓存但是没有网络的时候,直接使用缓存的结果
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//获取 Response,交给下一个拦截器
networkResponse = chain.proceed(networkRequest);
} finally {
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//当响应码为304,从缓存中获取数据
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
//http 头部是否有响应体,并且缓存策略可以被缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.写入 Response 到缓存中
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//判断 Request 方法是否是有效的缓存,是的话,移除这个缓存。
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
详情查看上面源码的注释。从源码中我们可以发现,OKHttp 的缓存主要是根据 CacheStrategy 这个缓存策略类来处理的,它决定了是使用网络还是缓存,又或是两者都用。源码最后可以看到,当有响应体且可以缓存时,缓存当前数据。然后还判断了,当请求是无效缓存时,要清除这个缓存。CacheInterceptor 缓存拦截器的整个逻辑就是这样了,重点在 CacheStrategy 这个缓存策略类。
ConnectInterceptor:
ConnectInterceptor
在 OKHTTP 底层是通过 SOCKET 的方式于服务端进行连接的,并且在连接建立之后会通过 OKIO 获取通向 server 端的输入流 Source 和输出流 Sink。
CallServerInterceptor:
CallServerInterceptor 在 ConnectInterceptor 拦截器的功能就是负责与服务器建立 Socket 连接,并且创建了一个 HttpStream 它包括通向服务器的输入流和输出流。而接下来的 CallServerInterceptor 拦截器的功能使用 HttpStream 与服务器进行数据的读写操作的。
拦截器创建总结:
- 创建一系列拦截器,并将其放入一个拦截器list中;
- 创建一个拦截器链RealInterceptorChain,并执行拦截器链的proceed方法;
拦截器创建总结2:
- 在发起请求前对request进行处理
- 调用下一个拦截器,获取response
- 对response进行处理,返回给上一个拦截器