OkHttp3 攔截器源碼分析
OkHttp 內置攔截器
在這篇博客 OkHttp3 攔截器(Interceptor) ,我們已經介紹了攔截器的作用,攔截器是 OkHttp 提供的對 Http 請求和響應進行統一處理的強大機制,它可以實現網絡監聽、請求以及響應重寫、請求失敗充實等功能。
同時也了解了攔截器可以被鏈接起來使用,我們可以註冊自定義的攔截器(應用攔截器和網絡攔截器)到攔截器鏈上,如下圖:
實際上除了我們自定義的攔截器外,OkHttp 系統內部還提供了幾種其他的攔截器,就是上圖中 OkHttp core 的部分。OkHttp 內部的攔截器各自負責不同的功能,每一個功能就是一個 Interceptor,這些攔截器連接起來形成了一個攔截器鏈,最終也就完成了一次網絡請求。
具體如下圖:
在上一篇博客 OkHttp3 源碼分析 中,我們分析了 OkHttp 的同步和異步請求的流程源碼,發現無論是同步請求還是異步請求都是通過調用 RealCall 的 getResponseWithInterceptorChain() 方法來獲取 response 響應的。
RealCall. getResponseWithInterceptorChain()源碼:
Response getResponseWithInterceptorChain() throws IOException { List<Interceptor> interceptors = new ArrayList(); //添加自定義的應用攔截器 interceptors.addAll(this.client.interceptors()); //負責重定向和失敗重試的攔截器 interceptors.add(this.retryAndFollowUpInterceptor); //橋接網絡層和應用層,就是為用戶所創建的請求補充添加一些服務端還必需的 http 請求頭等 interceptors.add(new BridgeInterceptor(this.client.cookieJar())); //負責讀取緩存,更新緩存 interceptors.add(new CacheInterceptor(this.client.internalCache())); //負責與服務端建立連接 interceptors.add(new ConnectInterceptor(this.client)); //配置自定義的網絡攔截器 if (!this.forWebSocket) { interceptors.addAll(this.client.networkInterceptors()); } //向服務端發送請求,從服務端讀取響應數據 interceptors.add(new CallServerInterceptor(this.forWebSocket)); //創建 攔截器鏈chain 對象,這裏將各種攔截器的 List 集合傳了進去 Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis()); //通過鏈式請求得到 response return chain.proceed(this.originalRequest); }
在這個方法中,我們就發現了 OkHttp 內置的這幾種攔截器,這幾種攔截器的具體作用稍後再說,先來宏觀的分析一下 getResponseWithInterceptorChain() 做了些什麽工作:
- 創建了一系列的攔截器,並將其放入一個攔截器 List 集合中。
- 將攔截器的 List 集合傳入 RealInterceptorChain 的構造方法中,創建出一個攔截器鏈 RealInterceptorChain 。
- 執行攔截器鏈 chain 的 proceed() 方法來依次調用每個不同功能的攔截器,最終獲取響應。
那麽這個 Chain 對象到底是如何處理攔截器集合的呢,為什麽通過調用 chain.proceed 就能得到被攔截器鏈依次處理之後的 response 呢?
其實這個問題的答案就是責任鏈設計模式,建議先了解一下關於責任鏈模式的介紹,再回頭往下看。
在理解了責任鏈模式之後,我們就能比較容易的理解攔截器是如何工作的了。
首先來看一看 Interceptor 接口,很明顯的它就是責任鏈模式中的抽象處理者角色了,各種攔截器都需要實現它的 intercept 方法
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
...
}
}
這裏我們註意到 Interceptor 還包含了一個內部接口 Chain,通過查看 Chain 接口,也可以大概了解它的功能:
- 通過 request() 方法來獲取 request 請求
- 通過 proceed(request) 方法來處理 request 請求,並返回 response 響應
剛剛也介紹了在 getResponseWithInterceptorChain() 方法中,正是由 Chain 來依次調用攔截器來獲取 response 的:
//創建 攔截器鏈chain 對象,這裏將各種攔截器的 List 集合傳了進去
Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());
//通過鏈式請求得到 response
return chain.proceed(this.originalRequest);
那麽它具體是怎麽工作的呢?我們先來看一下RealInterceptorChain 的構造方法
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
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;
}
需要特別註意的是這個構造方法裏的 index 參數,傳入給構造方法的 index 最終被賦值給了一個全局變量 index(這個變量很重要,之後會被使用到)。在構造出了 RealInterceptorChain 對象之後,接著就調用它的 proceed 方法來執行攔截器了。
來看一下 chain.proceed(request) 方法的具體實現:
RealInterceptorChain#proceed:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
...
// 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);
//調用當前的攔截器的 intercept 方法獲取 response
Response response = interceptor.intercept(next);
...
return response;
}
這個方法的關鍵邏輯在這與這三行代碼
一, 在 chain.proceed 的方法中,又 new 了一個 RealInterceptorChain,不過這裏傳入的參數是 index + 1,也就是說,每次調用 proceed 方法,都會產生出一個 index成員變量 +1的 RealInterceptorChain 對象。而且該 chain 對象的名字為 next,所以我們大致也能猜測一下它代表的是下一個 chain 對象。
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
二, 根據 index 索引值獲取當前攔截器,這個 index 就是之前創建 chain 構造函數時的 index 值
大家應該還記得在 getResponseWithInterceptorChain 第一次創建 Chain 對象時,index被初始化為0。
Interceptor interceptor = interceptors.get(index);
三, 調用當前攔截器的 intercept(Chain chain) 方法
Response response = interceptor.intercept(next);
這裏我們就以 index 為 0 為例,獲取 interceptors 集合中的第一個攔截器 RetryAndFollowUpInterceptor(假設沒有添加用戶自定義的應用攔截器),來看一下它的 intercept 方法:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We‘re throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
...
}
}
這段代碼中最關鍵的地方是:
response = realChain.proceed(request, streamAllocation, null, null);
我們發現,原來在 intercept() 中又會調用 chain.proceed() 方法,而每次調用 proceed 方法中又會去獲取一個索引為 index + 1 的下一個攔截器,並執行該攔截器的 intercept() 方法,就是這樣相互的遞歸調用,實現了對攔截器的逐步調用。
這個過程流程圖如下:
到這裏也許我們會有一個疑問,那就是為什麽每次都需要創建一個新的 RealInterceptorChain 對象,只需要修改 index 變量的值不是也能實現同樣的效果嗎?這裏的原因是 RealInterceptorChain 對象中還包含了 request 請求信息在內的其他信息,而每次執行攔截器的 intercept 方法時,因為遞歸調用的緣故,本層 的 intercept 並沒有被執行完,如果復用 RealInterceptorChain 對象,則其他層次會對本層次 RealInterceptorChain 對象產生影響。
參考
https://blog.csdn.net/aiynmimi/article/details/79643123
https://blog.csdn.net/qq_16445551/article/details/79008433
OkHttp3 攔截器源碼分析