標籤:資訊 assert out 部落格 ide 非同步請求 List 集合 throw lease
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 對象產生影響。
參考
79643123
79008433
OkHttp3 攔截器源碼分析