應用攔截器和網絡攔截器
以前其實就有一直在使用okhttp,也有聽說過攔截器這東西,但是一直沒有去深入了解。最近看《安卓進階之光》剛好看到okhttp攔截器的內容,然后自己也去挖了下源碼,才發現其巧妙之處。
攔截器有兩種,應用攔截器和網絡攔截器。用法可以看下面的代碼:
class LogInterceptor implements Interceptor {
private String mName;
LogInterceptor(String name) {
mName = name;
}
@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
Log.d("LogInterceptor", "[" + mName + "] : request url = " + response.request().url() + ", " + response.headers().toString());
return response;
}
}
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new LogInterceptor("ApplicationInterceptor"))
.addNetworkInterceptor(new LogInterceptor("NetworkInterceptor"))
.build();
Request request = new Request.Builder()
.url("http://www.github.com")
.build();
client.newCall(request).enqueue(null);
運行之后的打印如下:
12-29 00:07:02.378 12641-12859/com.example.okhttp D/LogInterceptor: [NetworkInterceptor] : request url = http://www.github.com/, Content-length: 0
Location: https://www.github.com/
12-29 00:07:03.653 12641-12859/com.example.okhttp D/LogInterceptor: [NetworkInterceptor] : request url = https://www.github.com/, Content-length: 0
Location: https://github.com/
12-29 00:07:04.889 12641-12859/com.example.okhttp D/LogInterceptor: [NetworkInterceptor] : request url = https://github.com/, Date: Thu, 28 Dec 2017 16:07:05 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Server: GitHub.com
Status: 200 OK
...(省略部分打印)
12-29 00:07:04.896 12641-12859/com.example.okhttp D/LogInterceptor: [ApplicationInterceptor] : request url = https://github.com/, Date: Thu, 28 Dec 2017 16:07:05 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Server: GitHub.com
Status: 200 OK
...(省略部分打印)
攔截器是一種強大的機制,可以在攔截器中進行監視、重寫和重試調用。像我們上面的代碼就對請求進行了監視。
從打印可以看到,網絡攔截器攔截到了三個請求,同時攔截到了重定向的訪問。而應用攔截器只攔截到了一個請求,同時我們可以看到它攔截到的請求的url是 https://github.com/ 和我們在代碼中的請求 http://www.github.com 并不一致。
簡單來講,網絡攔截器在每一次網絡訪問的時候都會攔截到請求,而應用攔截器只會在OkHttpClient.newCall返回的Call執行的時候被調用一次。
okhttp的運行流程
在講攔截器的實現之前我們先來簡單介紹一下okhttp的運行流程。
首先通過OkHttpClient.newCall我們可以獲得一個RealCall:
public class OkHttpClient implements Cloneable, Call.Factory {
...
public Call newCall(Request request) {
return new RealCall(this, request);
}
...
}
異步訪問
RealCall實現了Call。接口,我們通過調用enqueue方法可以實現異步網絡訪問。讓我們直接看看RealCall.enqueue吧:
final class RealCall implements Call {
...
public void enqueue(Callback responseCallback) {
enqueue(responseCallback, false);
}
void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}
...
}
client.dispatcher()可以獲得一個Dispatcher,它用于網絡訪問任務的調度,我們的異步并發網絡訪問就是通過Dispatcher實現的。這里創建了一個AsyncCall,然后將它傳入Dispatcher.enqueue。AsyncCall是RealCall的內部類,而且它實際上是一個Runnable:
final class RealCall implements Call {
...
final class AsyncCall extends NamedRunnable {
...
}
...
}
public abstract class NamedRunnable implements Runnable {
...
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
NamedRunnable在run方法里面會調用抽象的execute方法,在這個方法內部就會進行實際的網絡訪問。那Dispatcher.enqueue又做了寫什么呢?其實Dispatcher.enqueue實際上將AsyncCall這個Runnable放到了一個線程池中:
public final class Dispatcher {
...
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
...
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
...
}
一切明了,Call.enqueue實際上是將AsyncCall這個Runnable放到了線程池中執行去訪問網絡,而AsyncCall是RealCall的一個內部類,它持有RealCall的引用,所以在被線程池調用的時候可以獲得Request的信息。
所以將okhttp的異步流程簡化之后實際上就是Dispatcher中的線程池對Runnable的執行:
然后我們看看AsyncCall.execute的具體實現:
final class AsyncCall extends NamedRunnable {
...
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain(forWebSocket);
if (canceled) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
...
}
可以看到它是通過getResponseWithInterceptorChain來訪問網絡獲取Response的。
同步訪問
如果想用OkHttp去阻塞是的訪問網絡我們可以這樣調用:
Response response = client.newCall(request).execute();
這個execute是不是有點眼熟,但它是Call的一個方法,并不是我們上面異步訪問中提到的NamedRunnable.execute:
public interface Call {
...
Response execute() throws IOException;
..
}
現在我們來看看具體實現:
final class RealCall implements Call {
...
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
...
}
它也是通過getResponseWithInterceptorChain來訪問網絡獲取Response的。
攔截器的實現
我們在前面的小節中已經知道了,無論是同步還是異步,最終都是通過RealCall.getResponseWithInterceptorChain方法去訪問網絡的。但是在查看具體源代碼的時候發現在okhttp3.4.0-RC1開始其具體的實現細節有了一些不一樣的地方。所以我這邊分開兩部分來講一講okhttp3.4.0-RC1之前和之后攔截器的具體實現細節。
okhttp3.4.0-RC1之前的實現
okhttp3.4.0-RC1之前的RealCall.getResponseWithInterceptorChain 中實際上是調用了ApplicationInterceptorChain.proceed方法去訪問網絡獲取Response:
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
然后繼續看源碼,可以發現proceed內部會從OkHttpClient獲取序號為index的攔截器,并且創建新的序號加一的ApplicationInterceptorChain傳遞給攔截器去執行。于是有多少個攔截器就創建了多少個ApplicationInterceptorChain,他們會按照自己的序號調用對應的攔截器。這其實就是一種責任鏈模式的實現方式:
@Override public Response proceed(Request request) throws IOException {
// If there's another interceptor in the chain, call that.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
// No more interceptors. Do HTTP.
return getResponse(request, forWebSocket);
}
如果ApplicationInterceptorChain的序號大于OkHttpClient中注冊的攔截器的數量,則調用getResponse方法。這里ApplicationInterceptorChain是RealCall的內部類,getResponse調用的是RealCall.getResponse方法。
再看RealCall.getResponse方法,它內部有個while true的死循環,調用HttpEngine.sendRequest和HttpEngine.readResponse去發送請求和接收響應,如果出現了RouteException異常或者IOException異常則重新嘗試訪問:
Response getResponse(Request request, boolean forWebSocket) throws IOException {
...
while (true) {
...
try {
engine.sendRequest();
engine.readResponse();
releaseConnection = false;
} catch (RouteException e) {
HttpEngine retryEngine = engine.recover(e.getLastConnectException(), true, null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
throw e.getLastConnectException();
}catch (IOException e) {
HttpEngine retryEngine = engine.recover(e, false, null);
if (retryEngine != null) {
releaseConnection = false;
engine = retryEngine;
continue;
}
throw e;
}
...
}
我們繼續看engine.readResponse的實現,可以看到它調用了NetworkInterceptorChain.proceed方法去獲取響應:
public void readResponse() throws IOException {
...
Response networkResponse;
...
networkResponse = new NetworkInterceptorChain(0, networkRequest,
streamAllocation.connection()).proceed(networkRequest);
...
}
NetworkInterceptorChain.proceed和ApplicationInterceptorChain.proceed類似,也會不斷的創建新的NetworkInterceptorChain并且調用網絡攔截器,如果沒有網絡攔截器可以調用了,則會調用readNetworkResponse方法讀取響應:
@Override public Response proceed(Request request) throws IOException {
...
if (index < client.networkInterceptors().size()) {
NetworkInterceptorChain chain = new NetworkInterceptorChain(index + 1, request, connection);
Interceptor interceptor = client.networkInterceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
...
return interceptedResponse;
}
Response response = readNetworkResponse();
...
return response;
}
這里還有一點需要說明的是NetworkInterceptorChain是HttpEngine的內部類,它調用的readNetworkResponse方法實際上是HttpEngine.readNetworkResponse。現在我們就對OkHttp攔截器的請求流程和攔截器的實現原理有了比較全面的了解,下面這張圖對整個流程做一個總結:
okhttp3.4.0-RC1之后的實現
然后讓我們再來看一下3.4.0-RC1之后的實現:
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);
}
這里已經不再區分ApplicationInterceptorChain和NetworkInterceptorChain了,統一用RealInterceptorChain去處理:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
...
// 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);
...
return response;
}
這里將cookie處理、緩存處理、網絡連接都作為責任鏈的一部分,比起3.4.0.RC-1之前更加完全的實現了責任鏈模式。這里有必要講一下的就是retryAndFollowUpInterceptor, 它是一個RetryAndFollowUpInterceptor實例,它負責重連和重定向我們之前在3.4.0.RC-1之前看到的getResponse的while true就放到了這里來實現。
讓我們看看它的整個流程:
這樣的實現是不是以前要清晰很多?所有的步驟一目了然,看過原來的版本再看看3.4.0.RC-1重構后的版本,的確有一種眼前一亮的驚艷之感。果然好代碼都是需要一點點優化出來的。