前言
OkHttp 中的 Interceptor 是通過(guò)責(zé)任鏈模式來(lái)設(shè)計(jì)的, 責(zé)任鏈模式參考: 責(zé)任鏈模式 , 至于為什么需要使用該模式, 我的理解是一次完整的請(qǐng)求需要以下步驟
- 構(gòu)建業(yè)務(wù)請(qǐng)求數(shù)據(jù)
- 自定義公共 Header 數(shù)據(jù)
- 建立 Socket 連接
- 發(fā)送請(qǐng)求
- 緩存請(qǐng)求數(shù)據(jù)
那么對(duì)每一個(gè)步驟來(lái)講舔亭, 它是按照處理邏輯進(jìn)行排序, 并且每一個(gè)處理步驟都代表相應(yīng)的職責(zé)勇边,通常來(lái)講,編寫(xiě)代碼時(shí)不會(huì)將所有的邏輯都放到一堆去寫(xiě)队他, 因?yàn)楫?dāng)需要增添其他功能時(shí)將是巨大的災(zāi)難,徒役,這時(shí)責(zé)任鏈模式將派上巨大用場(chǎng)。
Interceptor 介紹
Interceptors are a powerful mechanism that can monitor, rewrite, and retry calls
如官網(wǎng)所稱, Interceptors 是一個(gè)強(qiáng)大的機(jī)制, 可以用來(lái) 監(jiān)控于游、重寫(xiě) request 毁葱、 重試等 。
在 Okhttp 中贰剥,攔截器同樣起到非常重要的作用倾剿,通過(guò)提供的默認(rèn)攔截器來(lái)實(shí)現(xiàn)了:建立連接、發(fā)送請(qǐng)求蚌成、處理響應(yīng)等前痘,其中 Interceptor 又根據(jù)使用場(chǎng)景劃分為 Application Interceptor 和 Network Interceptor ,Application Interceptor 是用來(lái)在整個(gè) okhttp client 應(yīng)用處理請(qǐng)求期間起作用且只被執(zhí)行一次担忧,而 Network Interceptor 則是在 okhttp client 應(yīng)用處理請(qǐng)求期間中的每一次網(wǎng)絡(luò)交互都會(huì)執(zhí)行一次(因?yàn)橛兄卦嚥呗约识龋钥赡軙?huì)出現(xiàn)處理一次應(yīng)用的請(qǐng)求需要多次網(wǎng)絡(luò)重試)。那么根據(jù)其場(chǎng)景可以選擇不同類(lèi)型攔截器進(jìn)行增強(qiáng)涵妥。
public class ApplicationLogInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long start = System.currentTimeMillis();
Response response = chain.proceed(request);
long end = System.currentTimeMillis();
if (end - start > 300) {
System.out.println(String.format("request cost time exceed=%s ms", end - start));
}
return response;
}
}
OkHttpClient httpClient = new OkHttpClient.Builder()
.callTimeout(500, TimeUnit.MILLISECONDS)
.connectTimeout(500, TimeUnit.MILLISECONDS)
.addInterceptor(new ApplicationLogInterceptor())
.build();
通過(guò)實(shí)現(xiàn) okhttp3.Interceptor 的 intercept 來(lái)達(dá)到對(duì)請(qǐng)求增強(qiáng)的目的乖菱,然后利用 OkHttpClient.Builder 的 addInterceptor(Interceptor interceptor)方法,將應(yīng)用攔截器的引用共享給 OkHttpClient 客戶端蓬网,這里與頂部圖片的 OkHttp Core 上半部分相同窒所。如果需要使用網(wǎng)絡(luò)攔截器,只需要將OkHttpClient.Builder 的 addInterceptor 方法換成 addNetworkInterceptor 即可帆锋。
注意這里的攔截器對(duì)所有請(qǐng)求都是屬于共享的吵取,因此所有類(lèi)變量使用不當(dāng)將會(huì)導(dǎo)致線程不安全,如果必須讓每個(gè)攔截器有“狀態(tài)”锯厢,那么可以通過(guò) ThreadLocal 來(lái)實(shí)現(xiàn)
Okhttp Core 中 默認(rèn)攔截器
Okhttp 將 http request 所經(jīng)過(guò)的每次請(qǐng)求鏈路的功能劃分給按職責(zé)劃分到不同攔截器中皮官,最終通過(guò)攔截器鏈 RealInterceptorChain 來(lái)組織,完成一次又一次的請(qǐng)求與響應(yīng)
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());
Response response = chain.proceed(originalRequest);
if (retryAndFollowUpInterceptor.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
}
在 RealCall 的 getResponseWithInterceptorChain() 方法中可以看到实辑,在執(zhí)行請(qǐng)求前捺氢,需要默認(rèn)的攔截器和業(yè)務(wù)自定義的攔截器添加到 interceptors 之后,最終交給 RealInterceptorChain 來(lái)調(diào)度 (突然想到 國(guó)不可一日無(wú)君,家不可一日無(wú)主)
- RetryAndFollowUpInterceptor
負(fù)責(zé)對(duì)請(qǐng)求進(jìn)行重試處理剪撬,前提是沒(méi)有在 OkHttpClient.Builder 中設(shè)置 .retryOnConnectionFailure(false)
摄乒,默認(rèn)是 true ,并且重試 21 次, google 瀏覽器也是這個(gè)策略馍佑,而 Http1.1 則是推薦 5 次斋否,沒(méi)辦法改重試的次數(shù),只有你把它 ban 了,然后自己寫(xiě)個(gè)應(yīng)用攔截器,具體看 RetryAndFollowUpInterceptor 源碼
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) {
...... 省略
} catch (IOException e) {
...... 省略
} finally {
...... 省略
}
....
這里可以看到 RouteException 和 IOException 這兩種異常的情況才有機(jī)會(huì)重試
- BridgeInterceptor
BridgeInterceptor負(fù)責(zé)在request階段對(duì)請(qǐng)求頭添加一些字段,在response階段對(duì)響應(yīng)進(jìn)行一些gzip解壓操作
@Override
public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
...... 省略
// 處理響應(yīng)數(shù)據(jù)
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
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)));
}
return responseBuilder.build();
...... 省略
- CacheInterceptor
緩存服務(wù)的請(qǐng)求與響應(yīng)
- ConnectInterceptor
創(chuàng)建一個(gè)新的連接,或從連接池服用連接
- CallServerInterceptor
這是攔截器鏈中的最后的攔截器辛慰,使用 ConnectInterceptor 的連接來(lái)向目標(biāo)服務(wù)器發(fā)送網(wǎng)絡(luò)請(qǐng)求,并讀取解析 Response 數(shù)據(jù)流