接上回 传送门
上回我们讲到,OkHttp的请求过程中有个非常重要的东西-“拦截器”,而且拦截器又分为interceptors和networkInterceptors两种,那它们具体有何区别呢?又要怎么来使用?现在来一探究竟
拦截器工作原理
在弄清楚区别之前,要先知道他们工作的原理,还是来到RealCall.execute方法里面的getResponseWithInterceptorChain:
RealCall.getResponseWithInterceptorChain
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());//自定义的拦截器
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
}
其实可以看出来,这个方法主要就是添加各种interceptor,然后文章最开始提到的第一个interceptors其实是自定义的拦截器,添加到了最前面,然后依次添加了四种拦截器过后,第五个是networkInterceptors,最后一共加了七种拦截器。
拦截器责任链
在这之后他们是怎么执行的?这就要说说Interceptor.Chain这个类了,其实从这个getResponseWithInterceptorChain方法名也可知一二,划重点了!这里用到了责任链模式,在请求的各个阶段都有相应的拦截器来负责,这些拦截器组成了一个链。在这里首先实例化成了RealInterceptorChain,来到这个类的构造方法来看看:
public RealInterceptorChain(List interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
这里主要是初始化了各种属性,这里面的参数大多数和OkHttp的属性无异,但需要注意的是这里的interceptors不同于OkHttp构造里面的那个interceptors,这里是表示刚才组成的所有拦截器的数组,包含interceptors和networkInterceptors在内的七种拦截器,然后index这个属性初始化为“0”。
接下来在getResponseWithInterceptorChain最后一句执行了它的proceed方法,在这个方法里有很多抛出异常的语句,这里不去深究,主要看看中间主要的一两句:
RealInterceptorChain.proceed
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 重点!!!主要就看看这几句
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);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
划重点!在这里就到了最核心的几句了
首先:又实例化了一个RealInterceptorChain,取名为next(从名字真的可以看出很多蹊跷),构造参数没变化,只是把index+1;
然后:从interceptors数组中用index来依次取出Interceptor(每次执行index都会+1,所以是依次取出来);
最后:调用interceptor.intercept(next),把next传入调用intercept方法
完;
这里如果直接点进去intercept方法会发现,这个方法在接口中:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
所以要去它的具体实现类去看看这个方法,从一开始的getResponseWithInterceptorChain方法可知,我们首先加入的拦截器是自定义的interceptors,第二个是retryAndFollowUpInterceptor,这里我们对拦截器的具体作用按下不表,先看看责任链的执行,找到RetryAndFollowUpInterceptor的intercept方法,方法很长,只截取了关于chain操作的部分:
RetryAndFollowUpInterceptor.intercept(Chain chain)
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
response = realChain.proceed(request, streamAllocation, null, null);
}
这个方法里面会对一些参数做修改,又调用了proceed方法,在proceed方法里我们知道index又会+1,然后取出下一个拦截器并且传入修改后的参数。这就完成了这个链式结构的运转,一步一步的往下传递,然后到了最后又依次返回Response
各种拦截器
看到这里,不禁会问,上面看到的那么多种拦截器到底分别是用来干啥的呢,在这里来总结一下(来自网络,只是做个归纳):
RetryAndFollowUpInterceptor
用来实现连接失败的重试和重定向
BridgeInterceptor
用来修改请求和响应的 header 信息
CacheInterceptor
用来实现响应缓存。比如获取到的 Response 带有 Date,Expires,Last-Modified,Etag 等 header,表示该 Response 可以缓存一定的时间,下次请求就可以不需要发往服务端,直接拿缓存的
ConnectInterceptor
用来打开到服务端的连接。其实是调用了 StreamAllocation 的newStream 方法来打开连接的。建联的 TCP 握手,TLS 握手都发生该阶段。过了这个阶段,和服务端的 socket 连接打通
CallServerInterceptor
用来发起请求并且得到响应。上一个阶段已经握手成功,HttpStream 流已经打开,所以这个阶段把 Request 的请求信息传入流中,并且从流中读取数据封装成 Response 返回
上一张图,看着直观一点(话说这个其实看着跟Android的点击事件分发机制很像啊)
最后来说说这个面试题
看到这里,对OkHttp的拦截器机制也了解了个大概,但是开篇提到的那个问题的答案到底是什么呢?
interceptors和networkInterceptors分别位于拦截器链的第一个和第六个,从每个拦截器的作用大致猜到一些。interceptors是肯定每次都会执行的,但是,在networkInterceptors之前有个ConnectInterceptor,这个拦截器的作用是用于建立跟服务器的连接。那如果我们现在设备离线,直接读取缓存呢?对,那这样的话networkInterceptors就不会执行了。
当然,这只是他们的一个最容易理解的区别,这个问题在OkHttp的官方文档已经给了我们答案 OkHttp文档中对于这两个拦截器的解释,下面来结合资料做个翻译
Application interceptors
- Don't need to worry about intermediate responses like redirects and retries.
- Are always invoked once, even if the HTTP response is served from the cache.
- Observe the application's original intent. Unconcerned with OkHttp-injected headers like If-None-Match.
- Permitted to short-circuit and not call Chain.proceed().
- Permitted to retry and make multiple calls to Chain.proceed().
- 不需要担心中间过程的响应,如重定向和重试
- 只会被调用一次,即使这个响应是从缓存中获取的
- 只关注最原始的请求, 不关心OkHttp注入的头信息如: If-None-Match
- 可以中断调用过程,有权决定是否要执行Chain.proceed()
- 允许重试,可以多次调用Chain.proceed()
Network Interceptors
Able to operate on intermediate responses like redirects and retries.
Not invoked for cached responses that short-circuit the network.
Observe the data just as it will be transmitted over the network.
Access to the Connection that carries the request.
能够操作中间过程的响应,如重定向和重试.
当返回缓存时不被调用.(也就是我们刚才举的例子)
观察在网络上传输的数据变化,比如重定向
携带请求来访问连接(这里已经建立了连接)
结题
看懂了拦截器其实也就是看懂了OkHttp,它的整个工作原理其实就是以拦截器责任链模式为核心,这种模式之下,我们可以很方便的来定制我们自己拦截器,比如可以改变请求头,处理缓存等等等等,不知道还有没有什么关于OkHttp的知识点呢?