在上一篇文章中,主要梳理了 OKHttp 請求的整體流程,了解到攔截器在其中有非常重要的意義,那么本篇文章就重點介紹一下 OKHttp 中的精髓 ---- 攔截器。本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。
- 攔截器的使用
- 源碼中攔截器的應用及分析
1. 攔截器的使用
攔截器是 OKHttp 設計的精髓所在,每個攔截器負責不同的功能,使用責任鏈模式,通過鏈式調用執行所有的攔截器對象中的 Response intercept(Chain chain)
方法。攔截器在某種程度上也借鑒了網絡協議中的分層思想,請求時從最上層到最下層,響應時從最下層到最上層。
一個攔截器可以攔截請求和響應,獲取或修改其中的信息,這在編程中是非常有用的。不僅在源碼中攔截器使用的很廣泛,開發者也可以根據自己的需求自定義攔截器,并將其加入到 OkHttpClient
對象中。
1.1 自定義攔截器
攔截器的源碼如下:
/**
* 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;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable Connection connection();
}
}
其中最重要的方法便是 Response intercept(Chain chain)
方法。自定義攔截器只要實現 interceptor
接口,重寫其中的 Response intercept(Chain chain)
方法便基本完成。
在開發中,經常想查看服務器響應中的內容,如果使用攔截器的話,則可以非常方便的實現,只要自定義攔截器并加入到 OKHttpClient
對象中,那么使用此 OKHttpClient
對象進行的網絡請求都會將其響應信息打印出來。自定義的攔截器 LogInterceptor,源碼如下所示:
public class LogInterceptor implements Interceptor {
private static final Charset UTF8 = Charset.forName("UTF-8");
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
long startTime = System.currentTimeMillis();
response = chain.proceed(request);
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
String log = "\n================="
.concat("\nnetwork code ==== " + response.code())
.concat("\nnetwork url ===== " + request.url())
.concat("\nduration ======== " + duration)
.concat("\nrequest duration ============ " + (response.receivedResponseAtMillis() - response.sentRequestAtMillis()))
.concat("\nrequest header == " + request.headers())
.concat("\nrequest ========= " + bodyToString(request.body()))
.concat("\nbody ============ " + buffer.clone().readString(UTF8));
Log.i("lijk", "log is " + log);
return response;
}
/**
* 請求體轉String
*
* @param request 請求體
* @return String 類型的請求體
*/
private static String bodyToString(final RequestBody request) {
try {
final Buffer buffer = new Buffer();
request.writeTo(buffer);
return buffer.readUtf8();
} catch (final Exception e) {
return "did not work";
}
}
}
1.2 注意事項
這里有個坑需要注意一下:response.body().string();
方法只能被調用一次,如果多次調用 response.body().string();
則會拋出如下異常:
可以看到 string()
的源碼如下所示:
/**
* Returns the response as a string decoded with the charset of the Content-Type header. If that
* header is either absent or lacks a charset, this will attempt to decode the response body in
* accordance to <a >its BOM</a> or UTF-8.
* Closes {@link ResponseBody} automatically.
*
* <p>This method loads entire response body into memory. If the response body is very large this
* may trigger an {@link OutOfMemoryError}. Prefer to stream the response body if this is a
* possibility for your response.
*/
public final String string() throws IOException {
BufferedSource source = source();
try {
Charset charset = Util.bomAwareCharset(source, charset());
return source.readString(charset);
} finally {
Util.closeQuietly(source);
}
}
因為在執行完讀取數據之后,IO 流被關閉,如果再次調用此方法,就會拋出上面的異常。
而且從注釋中可以看到,此方法將響應報文中的主體全部都讀到了內存中,如果響應報文主體較大,可能會導致 OOM 異常。所以更推薦使用流的方式獲取響應體的內容。如下所示:
BufferedSource source = response.body().source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
String response = buffer.clone().readString(UTF8);
2. 源碼中攔截器的應用及分析
在上一篇分析整體流程的文章中,在 RealCall
中有一個方法非常重要,不論是異步請求還是同步請求,都是通過該方法獲取服務器響應的,源碼如下所示:
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);
// 通過 RealInterceptorChain 對象鏈式的調用攔截器,從而得到響應。
return chain.proceed(originalRequest);
}
其中的每個攔截器負責具體不同的功能,接下來就分析每個攔截器的功能,因為攔截器中最重要的方法便是 Response intercept(Chain chain)
,所以我們也重點分析 Response intercept(Chain chain)
方法的實現。
2.1 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor
攔截器主要負責實現 HTTP 協議中的認證質詢、重定向和超時重試等協議機制。
RetryAndFollowUpInterceptor
攔截器的主要功能如下所示:
- 初始化連接對象
StreamAllocation
- 通過
RealInterceptorChain
調用鏈對象得到響應 - 通過得到的響應,根據 HTTP 協議做認證質詢、重定向和超時重試等處理,通過
followUpRequest()
方法創建后續新的請求 - 若沒有后續請求,即
followUpRequest()
方法返回為null
,則說明當前請求結束,返回響應
,若后續請求不為空,則繼續進行請求
源碼如下:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// 創建一個 StreamAllocation 對象
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
// 啟動一個 While 死循環
while (true) {
// 判斷是否已經取消,若已取消則拋出 IO 異常
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 通過 RealInterceptorChain 對象調用下一個攔截器,并從中得到響應
response = ((RealInterceptorChain) chain).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.
// 如果進入 RouteException 路由異常,則嘗試是否可以重新進行請求,若可以則從頭開始新的請求
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
// 若是進入 IOException IO異常,若可以重新嘗試請求,則從頭開始新的請求
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
// 如果沒有拋出異常,則釋放資源。
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
// 如果之前發生過重定向,并且 priorResponse 不為空,則創建新的 響應對象,并將其 body 置位空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 添加認證需要的頭部,處理重定向或超時重試,得到新的請求
// followUpRequest() 方法很重要,涉及到 HTTP 中認證質詢、重定向和重試等協議的實現
Request followUp = followUpRequest(response);
// 若 followUp 重試請求為空,則當前請求結束,并返回當前的響應
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 關閉響應結果
closeQuietly(response.body());
// 若重定向、認證質詢、重試次數超過 MAX_FOLLOW_UPS,則拋出 ProtocolException 異常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
// 若請求中的主體為 UnrepeatableRequestBody 不可被重復使用的請求體類型,則拋出異常
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 判斷是否是相同的連接,若不相同則釋放 streamAllocation,并重新創建新的 streamAllocation 對象
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()), callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
2.2 BridgeInterceptor
BridgeInterceptor
攔截器主要功能是:
- 設置一些請求和響應首部,如:
Content-Type
、Content-Length
、Host
等常見的請求和響應首部。 - 處理 HTTP 請求和響應中的 Cookie
- 如果在請求中設置了編碼,要從響應流中解碼
源碼如下:
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
// 在請求中設置實體首部 Content-Type
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
// 在請求中設置實體首部 Content-Length 和 Transfer-Encoding
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");
}
}
// 設置請求首部 `Host`
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
// 設置請求首部 Connection,若 `Connection` 為空,則打開長連接
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
// 如果在請求中設置了 "Accept-Encoding: gzip",要記得從響應流中解碼
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
requestBuilder.header("Accept-Encoding", "gzip");
}
// 為請求添加 Cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// 設置請求首部 "User-Agent"
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
// 從響應中得到 Cookie,并交給傳入的 CookieJar 對象處理
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// 如果之前在請求中設置了 "Accept-Encoding: gzip" 編碼,則需要對響應流進行解碼操作并移除響應中的首部字段 “Content-Encoding” 和 “Content-Length”
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
這里涉及到 OKHttp 中對 Cookie 的處理,其中有一個接口十分重要 ---- CookieJar
,源碼如下:
public interface CookieJar {
/** A cookie jar that never accepts any cookies. */
CookieJar NO_COOKIES = new CookieJar() {
@Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
// 從響應中獲取 Cookie
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
// 為請求添加 Cookie
List<Cookie> loadForRequest(HttpUrl url);
}
OKHttp 對 Cookie 的管理還是十分方便簡潔的。創建一個類實現 Cookiejar
接口,并將其對象設置給 OKHttpClient
,那么使用此 OKHttpClient
對象進行的網絡請求都會自動處理 Cookie。這里有一個我實現的自動管理 Cookie 的類 CustomCookieManager,可以實現 Cookie 的持久化,Github 上還有其他很好的實現在 OKHttp 中管理 Cookie 的類,也可以參考。
2.3 CacheInterceptor
CacheInterceptor
實現了 HTTP 協議中的緩存機制,其主要功能如下:
- 從緩存中讀取緩存,并創建緩存策略對象
- 根據創建的緩存策略對象,從緩存、網絡獲取響應并生成最終的響應對象
- 更新緩存內容,并返回響應對象
源碼如下:
public final class CacheInterceptor implements Interceptor {
final InternalCache cache;
public CacheInterceptor(InternalCache cache) {
// 從 OKHttpClient 中傳入的 InternalCache(內部緩存)對象
this.cache = cache;
}
@Override public Response intercept(Chain chain) throws IOException {
// 獲取候選緩存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 創建緩存策略對象,并從中得到請求和響應
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 如果不走網絡進行請求,并且緩存響應為空,則創建狀態碼為 504 的響應并返回
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
// 如果不走網絡進行請求,并且緩存響應不為空,則返回從緩存中獲取的響應
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 如果前面的條件都不滿足,從攔截器鏈中進行網絡請求并得到響應
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
// 如果緩存響應也不為空,并且網絡響應的狀態碼為 304,則根據緩存響應結果生成最終的響應并返回
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
// 更新緩存中的響應內容
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 根據網絡請求響應生成最終的響應
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 如果緩存對象不為空,則將響應加入到緩存中
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
......
}
2.4 ConnectInterceptor
ConnectInterceptor
攔截器的主要功能是:
- 得到從
RetryAndFollowUpInterceptor
中創建的StreamAllocation
對象 - 通過
StreamAllocation
對象創建HttpCodec
對象 - 通過
StreamAllocation
對象創建RealConnection
對象 - 最終通過
RealInterceptorChain
的proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,RealConnection connection)
方法得到響應對象并返回。
源碼如下:
/** Opens a connection to the target server and proceeds to the next interceptor. */
public final class ConnectInterceptor implements Interceptor {
public final OkHttpClient client;
public ConnectInterceptor(OkHttpClient client) {
this.client = client;
}
@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, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
在
ConnectInterceptor
攔截器中用到了StreamAllocation
類,此類創建了HttpCodec
和RealConnection
對象,這兩個類在網絡請求中都是非常重要的類,會在后面文章中詳細分析
2.5 CallServerInterceptor
CallServerInterceptor
攔截器中最主要的功能就是:
- 遵循 HTTP 協議規范,通過
HttpCodec
對象寫入請求頭、請求主體、讀取響應頭和響應主體 - 生成最初的響應對象并返回
源碼如下所示:
@Override public Response intercept(Chain chain) throws IOException {
// 得到 httpCodec、streamAllocation、connection 和 request 對象
RealInterceptorChain realChain = (RealInterceptorChain) chain;
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
// 向 HttpCodec 對象中寫入請求頭部信息
long sentRequestMillis = System.currentTimeMillis();
httpCodec.writeRequestHeaders(request);
Response.Builder responseBuilder = null;
// 判斷該請求的請求方法是否允許被發送請求體,請求體是否為空
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
// If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
// Continue" response before transmitting the request body. If we don't get that, return what
// we did get (such as a 4xx response) without ever transmitting the request body.
// 若在請求頭部中存在 ”Expect: 100-continue“,先不發送請求主體,只有收到 ”100-continue“ 響應報文才會將請求主體發送出去。
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} else if (!connection.isMultiplexed()) {
// If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection from
// being reused. Otherwise we're still obligated to transmit the request body to leave the
// connection in a consistent state.
streamAllocation.noNewStreams();
}
}
// 完成請求的發送
httpCodec.finishRequest();
// 讀取響應頭部信息
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
// 創建請求響應對象
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
// 判斷是否返回一個空的響應
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
// 讀取響應中的響應體信息
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
// 判斷是否關閉長連接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
// 如果響應的狀態碼為 204 和 205 并且響應體不為空,則拋出異常
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
在此攔截器中非常重要的一個對象是 HttpCodec,它是一個接口,具體的實現類有 Http1Codec 和 Http2Codec 兩個類,分別對應著 HTTP1.1 和 HTTP2。在 HttpCodec 內部是通過
sink
和source
來實現的。
2.6 注意
關于 OKHttp 的攔截器需要注意的一點是,從 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);
return chain.proceed(originalRequest);
}
一種是在最開始,通過 client.interceptors()
方法添加的普通攔截器;另一種是通過 client.networkInterceptors()
方法添加的網絡請求攔截器,兩者的區別如下:
- 普通攔截器:對發出去的請求做最初的處理,對最終得到的響應做處理
- 網絡攔截器:對發出去的請求做最后的處理,對收到的響應做最初的處理
兩種攔截器都可以通過在初始化 OKHttpClient
對象的時候設置,如下所示:
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor())
.addNetworkInterceptor(new LogInterceptor())
.build();
雖然都是添加了 LogInterceptor
攔截器給 OKHttpClient
對象,但是從 RealCall
的 getResponseWithInterceptorChain()
方法中可以看出兩個攔截器調用的時機不同。
本文中涉及到的自定義類的源碼都在 Github 上的 OkHttpPractice 工程中。
參考資料:
OkHttp源碼解析 -- 俞其榮
OkHttp源碼解析 -- 高沛