前言:被一位阿里面試官問到過這個問題,“讓我談一下okhhtp攔截器的呼叫順序及影響。”
我腦子裡只有責任鏈的設計模式,但是幾種攔截器的順序我確實沒記憶過,有點尷尬。
所以呢,就來稍微研究一下,順便記憶一下,正所謂“面試造航母,工作擰螺絲。”
複製程式碼
1)基本用法
此次我看的原始碼是v3.9.0, 首先我們來看一下官網上基本用法(get請求為例),如下
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
Response response = client.newCall(request).execute();
String jsonStr=response.body().string()
複製程式碼
可以看到主要分四步:1.構建OkHttpClient物件;2.構建Request物件;3.呼叫client.newCall(request)得到Call物件;4.Call物件執行同步方法execute(),或者非同步方法enqueue(Callback responseCallback),得到響應的內容.
2)請求過程原始碼分析
2.1 OkhttpClient構建
public OkHttpClient() {
this(new Builder());
}
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
this.proxy = builder.proxy;
this.protocols = builder.protocols;
this.connectionSpecs = builder.connectionSpecs;
this.interceptors = Util.immutableList(builder.interceptors);
this.networkInterceptors = Util.immutableList(builder.networkInterceptors);
this.eventListenerFactory = builder.eventListenerFactory;
this.proxySelector = builder.proxySelector;
this.cookieJar = builder.cookieJar;
this.cache = builder.cache;
this.internalCache = builder.internalCache;
this.socketFactory = builder.socketFactory;
...剔除部分原始碼
this.hostnameVerifier = builder.hostnameVerifier;
this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
certificateChainCleaner);
this.proxyAuthenticator = builder.proxyAuthenticator;
this.authenticator = builder.authenticator;
this.connectionPool = builder.connectionPool;
this.dns = builder.dns;
this.followSslRedirects = builder.followSslRedirects;
this.followRedirects = builder.followRedirects;
this.retryOnConnectionFailure = builder.retryOnConnectionFailure;
this.connectTimeout = builder.connectTimeout;
this.readTimeout = builder.readTimeout;
this.writeTimeout = builder.writeTimeout;
this.pingInterval = builder.pingInterval;
....
}
public static final class Builder {
Dispatcher dispatcher;
@Nullable Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable Cache cache;
@Nullable InternalCache internalCache;
SocketFactory socketFactory;
@Nullable SSLSocketFactory sslSocketFactory;
@Nullable CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
eventListenerFactory = EventListener.factory(EventListener.NONE);
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
pingInterval = 0;
}
Builder(OkHttpClient okHttpClient) {
this.dispatcher = okHttpClient.dispatcher;
this.proxy = okHttpClient.proxy;
this.protocols = okHttpClient.protocols;
this.connectionSpecs = okHttpClient.connectionSpecs;
this.interceptors.addAll(okHttpClient.interceptors);
this.networkInterceptors.addAll(okHttpClient.networkInterceptors);
this.eventListenerFactory = okHttpClient.eventListenerFactory;
this.proxySelector = okHttpClient.proxySelector;
this.cookieJar = okHttpClient.cookieJar;
this.internalCache = okHttpClient.internalCache;
this.cache = okHttpClient.cache;
this.socketFactory = okHttpClient.socketFactory;
this.sslSocketFactory = okHttpClient.sslSocketFactory;
this.certificateChainCleaner = okHttpClient.certificateChainCleaner;
this.hostnameVerifier = okHttpClient.hostnameVerifier;
this.certificatePinner = okHttpClient.certificatePinner;
this.proxyAuthenticator = okHttpClient.proxyAuthenticator;
this.authenticator = okHttpClient.authenticator;
this.connectionPool = okHttpClient.connectionPool;
this.dns = okHttpClient.dns;
this.followSslRedirects = okHttpClient.followSslRedirects;
this.followRedirects = okHttpClient.followRedirects;
this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure;
this.connectTimeout = okHttpClient.connectTimeout;
this.readTimeout = okHttpClient.readTimeout;
this.writeTimeout = okHttpClient.writeTimeout;
this.pingInterval = okHttpClient.pingInterval;
}
public OkHttpClient build() {
return new OkHttpClient(this);
}
}
複製程式碼
可以看到Client物件是通過建造者模式設計的,直接new OkhttpClient()方式,傳入預設的builder,或者通過配置builder,呼叫build()構建;
2.2 client.newCall(Request request
Request物件也是通過建造者模式構建的,根據不同的請求構建不同的Request物件;
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
/**
我們可以看到,client呼叫.newCall(request),實際呼叫的是RealCall.newRealCall(this, request, false),返回Call物件,說明Realcall應該是實現了Call介面;
在去看一下RealCall.newCall都幹了些什麼;
*/
final class RealCall implements Call {
...
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//可以看到RealCall實現了Call介面,newRealCall 內部實際是將入參傳入構造,構建了RealCall物件,建立一個call的事件監聽;
在看看RealCall構造幹了什麼;
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
//內部將入參client,原始的請求賦值,同時構建了RetryAndFollowUpInterceptor()攔截器;
//該攔截器的作用就是發生錯誤時,進行重定向;
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
}
複製程式碼
2.3 call.excute() call.enqueue(callback)
我們繼續跟進拿到call物件後,真正發起網路請求的操作 繼續看call的實現類RealCall
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//開始監聽call請求
eventListener.callStart(this);
try {
//通過client的dispatcher,分發請求;Dispather中構建了一個執行緒池,所有的請求會加入到執行緒池中執行;
//這裡
client.dispatcher().executed(this);
//這個方法很關鍵,也是Okhttp攔截器的責任鏈呼叫的開始,是這個庫的精華所在,可以看到,在這裡返回了伺服器響應的內容
//我已經迫不及待一探究竟了,哈哈哈哈哈哈= =
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
//開始監聽
eventListener.callStart(this);
//非同步呼叫
client.dispatcher().enqueue(new AsyncCall(responseCallback));
/**
*dispatcher物件的enqueue方法,可以看到,如果儲存非同步Call的集合小於最大請求數,並且未達到同一個host最大的連線數
*那麼直接加入待執行佇列,呼叫execute,執行;否則,先加入預執行佇列
*/
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
}
複製程式碼
2.4 攔截器責任鏈的呼叫 getResponseWithInterceptorChain()
為了方便大家理解,請允許我先上個圖:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
//1.構建一個空集合interceptors
List<Interceptor> interceptors = new ArrayList<>();
//2.可以看到首先會將client的interceptors全部加入這個集合頭部,這個client的interceptors就是我們在構建Client Builder時,呼叫
// addInterceptor(Interceptor interceptor)加入的自定義攔截器,也就是所謂的應用攔截器;
interceptors.addAll(client.interceptors());
//3.尾部追加重定向攔截器 retryAndFollowUpInterceptor,還記得這個攔截器是什麼時候建立的嗎?
//沒錯,就是在new RealCall的時候構建的.
interceptors.add(retryAndFollowUpInterceptor);
//4.尾部新增BridgeInterceptor,設定預設的cookjar,這是一個請求和響應的聯結器
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//5.新增快取攔截器,處理一些網路快取邏輯,控制走網咯還是走快取等
interceptors.add(new CacheInterceptor(client.internalCache()));
//6.新增網路連線攔截器
interceptors.add(new ConnectInterceptor(client));
//7.如果不是websocket,還新增了網路攔截器,新增自定義連線攔截器(比如我們常用的facebook除錯框架的開源框架stecho)
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//8.新增了請求服務攔截器
interceptors.add(new CallServerInterceptor(forWebSocket));
// 以上是存放在arryList中intercptor的順序,
//9.可以看到接下來就是構建責任鏈Chain, 其實現類是RealInterceptorChain,
// RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
// HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
// EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout)
//10.其中,我們最主要來關注一下 index這個入參
//可以看到,鏈的開始index入參為0,我們繼續跟進一下chain.proceed(originalRequest)幹了什麼;
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
/*==========================*/
public Builder addInterceptor(Interceptor interceptor) {
if (interceptor == null) throw new IllegalArgumentException("interceptor == null");
interceptors.add(interceptor);
return this;
}
public List<Interceptor> interceptors() {
return interceptors;
}
//進入Chain的實現類RealInterceptorChain,看一下proceed做了哪些事情
//我們看一些關鍵的地方.
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
//11.首先判斷index是否超出攔截器索引,超出就跑異常,終止責任鏈
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// 12.構建下一個chain物件,可以看到此處index+1,
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//13.從集合中取出下一個攔截器,呼叫 interceptor.intercept(chain)返回Response;我們可以隨便看一個interceptor的實現類,
//研究一下interceptor.intercept(chain) 是如何返回Response的;
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
複製程式碼
2.5 攔截器責任鏈的呼叫 interceptor.intercept(chain)
我們就以CacheInterceptor為例,看看它的intercept() fun都做了哪些事情.
@Override public Response intercept(Chain chain) throws IOException {
...省略部分原始碼,我們只看關鍵程式碼
//1.可以看到這裡判斷了網路請求是否存在,是否有響應快取,兩則都null,時,構建一個504錯誤碼的Resonse並返回
// 該方法return
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();
}
// 2.判斷無請求網路,返回快取內容,方法return
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//3.上述兩種情況都不滿足,走到這裡,呼叫chain.proceed(request)
//看到這個Api是否似曾相識呢~~哈哈哈哈哈,回到getResponseWithInterceptorChain()方法,其就是呼叫了
//chain.proceed(originalRequest)得到Response返回;
//而,chain.proceed 中如上分析,會把index++,構建下一chain,至此,責任鏈就完整形成了.
//前一個chain.proceed 返回值依賴後一個chain.proceed返回,由此遞迴,直到index 超出丟擲超異常.
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());
}
}
....省略程式碼
return response;
}
複製程式碼
3)總結
我們可以看到Okhttp在構建Client和Request物件時用到了Builder模式,這才我們開發中也使用場景也很多,尤其是引數很多的情況下,通過構造或者set方法初始化就不是特別友好;Interceptor的責任鏈模式設計模式也很巧妙,我們可以通過很輕鬆的新增或刪除攔截器實現許多業務場景,但是細想一下也不難發現,責任鏈模式如果鏈太長的情況下會發生什麼?方法不斷壓棧,只有鏈呼叫結束或者異常時,才會出棧,釋放棧空間. 有哪裡寫錯或理解錯誤的歡迎討論指出~~