接上回傳送門
上回我們講到,OkHttp的請求過程中有個非常重要的東西-“攔截器”,而且攔截器又分爲interceptors和networkInterceptors兩種,那它們具體有何區別呢?又要怎麼來使用?現在來一探究竟
攔截器工作原理
在弄清楚區別之前,要先知道他們工作的原理,還是來到RealCall.execute方法裏面的getResponseWithInterceptorChain:
RealCall.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()));
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<Interceptor> 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的知識點呢?