這篇文章,對okhttp的另一個非常重要的概念-攔截器(Interceptor)進行源碼分析。或許,有的朋友就要說了,前面兩篇文章分別總結了兩種請求的源碼以及Dispatcher的源碼,爲什麼突然扯到Interceptor了呢?接下來,我們先了解一下,攔截器是什麼。
一、Interceptor是什麼
Interceptor翻譯過來就叫攔截器。引用官網的解釋:攔截器是okhttp的一種強大的機制。他可以實現網絡監聽、請求以及重寫響應、請求失敗重試等功能。
二、從同步請求說起
我們前面的源碼分析,在講同步請求的時候,我們對response的獲取一筆帶過,並沒有深入去追究response是如何獲取到的。我們先把同步請求執行的相關源碼貼一下:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
上面的代碼,我們在同步請求源碼解析中,大部分都做了解析。但是,有很重要的一行,我一筆帶過。也就是:
Response result = getResponseWithInterceptorChain();
這個方法,乍一看很簡單,就是在發起請求後,獲取到請求的結果。但實際上真的那麼簡單嗎?當然不是。我們通過同步請求和異步請求的執行可以看出,請求的過程特別是異步請求還是比較麻煩的。因此。請求結果的獲取,也並不是一行代碼就輕鬆得到的,而是通過攔截器鏈一步一步執行獲得的。
三、攔截器
首先,我們看一下okhttp的攔截器鏈:
我們接着上面的方法,跟進去源碼。我們看到,代碼量不多,就十幾行代碼。但是,我們看到了這中間有很多個我們從來沒見到過的類,各種XXXInterceptor。這其實就是okhttp的攔截器鏈:
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);
}
簡單說一下這個方法,其實,如果我們忽視掉這些攔截器的功能,單純看代碼的話,還是比較好理解的。首先,我們往一個攔截器的List中添加了很多個攔截器。然後,我們通過這個攔截器的List等參數去創建了一個攔截器鏈,注意,這個地方傳入的index爲0,也就是第一個攔截器。最後,這個攔截器鏈調用proceed方法。可見,核心的代碼實現是在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");
}
// Call the next interceptor in the chain.
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;
}
我們看一下幾處核心代碼,首先是下面這一段。我們看到,跟我們在進入proceed方法前一樣,創建了一個攔截器鏈,但是這裏的index是index+1,也就是攔截器鏈中當前index的下一個攔截器,這其實就是攔截器鏈的實現。然後,獲取當前index的攔截器,去執行下一個攔截器,代碼如下:
// Call the next interceptor in the chain.
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);
我們看一下intercept方法的實現。Interceptor是一個接口,我們看一下實現類的方法實現。在上面我們說過,攔截器有很多個,他們是鏈式調用的。第一個攔截器,其實就是RetryAndFollowUpInterceptor,它的intercept方法具體實現在這裏,我們先不去關注具體實現,我們只看下面這麼一行代碼:
response = realChain.proceed(request, streamAllocation, null, null
是的,每個攔截器的intercept方法,其實又調用了proceed方法,而proceed方法,又去調用下一個攔截器的intercept方法。接下來,我們簡單看一下每一種攔截器的作用。
1、RetryAndFollowUpInterceptor
這個中文名字,我們叫他重試和跟蹤攔截器。他的主要作用就是發起網絡請求,如果該請求需要重定向,那麼進行重定向請求,並且在請求失敗的情況下,發起重試。
(1)創建網絡請求
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
(2)重定向
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
streamAllocation.release();
return response;
}
(3)重試
重試次數不是無限的,在這裏設置的是20次。
private static final int MAX_FOLLOW_UPS = 20;
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
2、BridgeInterceptor
中文叫橋接攔截器,他的主要作用是添加頭部信息(例如User-Agent,,Host,Cookie等),使請求成爲一個標準的http請求併發起請求,並且處理返回給我們的response,使之轉化爲用戶可用的response。
(1)添加頭部信息
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
(2)處理response
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)));
3、CacheInterceptor
中文叫緩存攔截器。試想一下,是不是我們每次發起網絡請求都真正的去服務器請求呢?當然不是,Okhttp也有自己的緩存策略。當我們發起請求的時候,會去判斷是否有緩存,如果有緩存則直接從緩存中拿這個請求。如果沒有緩存,那麼去使用網絡發起請求。這些緩存功能的實現,就是緩存攔截器來做的。
4、ConnectInterceptor
網絡連接攔截器,在經過上面一系列攔截器的處理後(是否重試和重定向,拼接頭部信息,是否使用緩存等),終於到了和服務器連接的時刻,網絡連接攔截器就是和服務器進行連接的攔截器。我們看一下源碼:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
網絡連接攔截器的工作流程如下:
(1)獲取前面的攔截器傳過來的StreamAllocation
(2)把StreamAllocation,httpCodec,RealConnection等等傳遞給後面的攔截器。
HttpCodec是一個非常關鍵的類,看到Codec,我們能猜出它的作用,一般是編解碼相關的類。HttpCodec就是負責對http請求和響應進行編解碼的類。
RealConnection也是一個非常關鍵的類,他是真正的網絡連接類,它維護了一個網絡連接池ConnectionPool。
5、CallServerInterceptor
訪問服務端攔截器。這個攔截器從名字可以看出,它是真正向服務端發起請求的攔截器。這裏面都是和網絡請求相關的一些方法和特殊情況的處理,最後會返回我們所需要的response。
最後,簡單總結一下,okhttp有多個攔截器,這些攔截器一起構成了攔截器鏈,每一個鏈接器在執行自己功能的同時,會調用下一個攔截器,同時對response進行處理,返回上一個攔截器,這樣完成了攔截器的鏈式調用。