應用攔截器和網(wǎng)絡(luò)攔截器
以前其實就有一直在使用okhttp,也有聽說過攔截器這東西,但是一直沒有去深入了解。最近看《安卓進階之光》剛好看到okhttp攔截器的內(nèi)容,然后自己也去挖了下源碼,才發(fā)現(xiàn)其巧妙之處长搀。
攔截器有兩種,應用攔截器和網(wǎng)絡(luò)攔截器宇弛。用法可以看下面的代碼:
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
...(省略部分打印)
攔截器是一種強大的機制,可以在攔截器中進行監(jiān)視、重寫和重試調(diào)用源请。像我們上面的代碼就對請求進行了監(jiān)視枪芒。
從打印可以看到,網(wǎng)絡(luò)攔截器攔截到了三個請求,同時攔截到了重定向的訪問。而應用攔截器只攔截到了一個請求,同時我們可以看到它攔截到的請求的url是 https://github.com/ 和我們在代碼中的請求 http://www.github.com 并不一致谁尸。
簡單來講,網(wǎng)絡(luò)攔截器在每一次網(wǎng)絡(luò)訪問的時候都會攔截到請求,而應用攔截器只會在OkHttpClient.newCall返回的Call執(zhí)行的時候被調(diào)用一次舅踪。
okhttp的運行流程
在講攔截器的實現(xiàn)之前我們先來簡單介紹一下okhttp的運行流程。
首先通過OkHttpClient.newCall我們可以獲得一個RealCall:
public class OkHttpClient implements Cloneable, Call.Factory {
...
public Call newCall(Request request) {
return new RealCall(this, request);
}
...
}
異步訪問
RealCall實現(xiàn)了Call症汹。接口,我們通過調(diào)用enqueue方法可以實現(xiàn)異步網(wǎng)絡(luò)訪問硫朦。讓我們直接看看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,它用于網(wǎng)絡(luò)訪問任務的調(diào)度,我們的異步并發(fā)網(wǎng)絡(luò)訪問就是通過Dispatcher實現(xiàn)的贷腕。這里創(chuàng)建了一個AsyncCall,然后將它傳入Dispatcher.enqueue背镇。AsyncCall是RealCall的內(nèi)部類,而且它實際上是一個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方法里面會調(diào)用抽象的execute方法,在這個方法內(nèi)部就會進行實際的網(wǎng)絡(luò)訪問咬展。那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放到了線程池中執(zhí)行去訪問網(wǎng)絡(luò),而AsyncCall是RealCall的一個內(nèi)部類,它持有RealCall的引用,所以在被線程池調(diào)用的時候可以獲得Request的信息瞒斩。
所以將okhttp的異步流程簡化之后實際上就是Dispatcher中的線程池對Runnable的執(zhí)行:
然后我們看看AsyncCall.execute的具體實現(xiàn):
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來訪問網(wǎng)絡(luò)獲取Response的破婆。
同步訪問
如果想用OkHttp去阻塞是的訪問網(wǎng)絡(luò)我們可以這樣調(diào)用:
Response response = client.newCall(request).execute();
這個execute是不是有點眼熟,但它是Call的一個方法,并不是我們上面異步訪問中提到的NamedRunnable.execute:
public interface Call {
...
Response execute() throws IOException;
..
}
現(xiàn)在我們來看看具體實現(xiàn):
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來訪問網(wǎng)絡(luò)獲取Response的。
攔截器的實現(xiàn)
我們在前面的小節(jié)中已經(jīng)知道了,無論是同步還是異步,最終都是通過RealCall.getResponseWithInterceptorChain方法去訪問網(wǎng)絡(luò)的胸囱。但是在查看具體源代碼的時候發(fā)現(xiàn)在okhttp3.4.0-RC1開始其具體的實現(xiàn)細節(jié)有了一些不一樣的地方。所以我這邊分開兩部分來講一講okhttp3.4.0-RC1之前和之后攔截器的具體實現(xiàn)細節(jié)。
okhttp3.4.0-RC1之前的實現(xiàn)
okhttp3.4.0-RC1之前的RealCall.getResponseWithInterceptorChain 中實際上是調(diào)用了ApplicationInterceptorChain.proceed方法去訪問網(wǎng)絡(luò)獲取Response:
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
然后繼續(xù)看源碼,可以發(fā)現(xiàn)proceed內(nèi)部會從OkHttpClient獲取序號為index的攔截器,并且創(chuàng)建新的序號加一的ApplicationInterceptorChain傳遞給攔截器去執(zhí)行收津。于是有多少個攔截器就創(chuàng)建了多少個ApplicationInterceptorChain,他們會按照自己的序號調(diào)用對應的攔截器澄耍。這其實就是一種責任鏈模式的實現(xiàn)方式:
@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中注冊的攔截器的數(shù)量,則調(diào)用getResponse方法。這里ApplicationInterceptorChain是RealCall的內(nèi)部類,getResponse調(diào)用的是RealCall.getResponse方法谤职。
再看RealCall.getResponse方法,它內(nèi)部有個while true的死循環(huán),調(diào)用HttpEngine.sendRequest和HttpEngine.readResponse去發(fā)送請求和接收響應,如果出現(xiàn)了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;
}
...
}
我們繼續(xù)看engine.readResponse的實現(xiàn),可以看到它調(diào)用了NetworkInterceptorChain.proceed方法去獲取響應:
public void readResponse() throws IOException {
...
Response networkResponse;
...
networkResponse = new NetworkInterceptorChain(0, networkRequest,
streamAllocation.connection()).proceed(networkRequest);
...
}
NetworkInterceptorChain.proceed和ApplicationInterceptorChain.proceed類似,也會不斷的創(chuàng)建新的NetworkInterceptorChain并且調(diào)用網(wǎng)絡(luò)攔截器,如果沒有網(wǎng)絡(luò)攔截器可以調(diào)用了,則會調(diào)用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的內(nèi)部類,它調(diào)用的readNetworkResponse方法實際上是HttpEngine.readNetworkResponse。現(xiàn)在我們就對OkHttp攔截器的請求流程和攔截器的實現(xiàn)原理有了比較全面的了解,下面這張圖對整個流程做一個總結(jié):
okhttp3.4.0-RC1之后的實現(xiàn)
然后讓我們再來看一下3.4.0-RC1之后的實現(xiàn):
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);
}
這里已經(jīng)不再區(qū)分ApplicationInterceptorChain和NetworkInterceptorChain了允蜈,統(tǒng)一用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處理冤吨、緩存處理、網(wǎng)絡(luò)連接都作為責任鏈的一部分饶套,比起3.4.0.RC-1之前更加完全的實現(xiàn)了責任鏈模式漩蟆。這里有必要講一下的就是retryAndFollowUpInterceptor, 它是一個RetryAndFollowUpInterceptor實例,它負責重連和重定向我們之前在3.4.0.RC-1之前看到的getResponse的while true就放到了這里來實現(xiàn)妓蛮。
讓我們看看它的整個流程:
這樣的實現(xiàn)是不是以前要清晰很多怠李?所有的步驟一目了然,看過原來的版本再看看3.4.0.RC-1重構(gòu)后的版本蛤克,的確有一種眼前一亮的驚艷之感扔仓。果然好代碼都是需要一點點優(yōu)化出來的。