對OkHttp介紹,我們分爲使用篇和源碼分析篇兩個系列進行介紹。
在上一篇文章中,我們大致梳理了一下OkHttp的請求過程。但是中間涉及到的client.dispatcher().executed(this);
、client.dispatcher().enqueue(new AsyncCall(responseCallback));
和getResponseWithInterceptorChain()
這幾個過程我們並沒有進入去詳細分析,只是提了每一個方法的作用。所以本篇文章的內容就是對這幾個方法進行說明。
首先,前兩個方法都是通過client.dispatcher()
獲得了Dispatcher
對象,然後調用它對應的方法。這裏我們就先介紹下Dispatcher
這個OkHttp框架中的任務調度類。
1 Dispatcher
在纖細介紹該類之前,我們先看下爲什麼通過client.dispathc()
就能獲得到Dispatcher對象了,也就是先介紹下該對象的創建過程是怎麼樣的。
1.1 Dispatcher的創建過程
各位應該還記得我們OkHttp的基本使用方式吧:
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
第一步就是創建OkHttpClient對象,我們看下它的構造方法:
public OkHttpClient() {
this(new Builder());
}
創建Builder
後,調用了它的有參構造,我們先看下Builder
的構造過程:
public Builder() {
dispatcher = new Dispatcher();
……
}
這裏省略一些代碼,但是可以看到,在Builder
的構造方法中,初始化了一個Dispatcher
對象。然後我們在回到上一步流程,調用OkHttpClient的
有參構造:
OkHttpClient(Builder builder) {
this.dispatcher = builder.dispatcher;
……
}
也省略了無關代碼,可以看到,這裏就將Builder中的dispather
賦值給了OkHttpClient
中的dispatcher屬性。所以我們通過client.dispater()
就能獲取到Dispatcher
對象了:
public Dispatcher dispatcher() {
return dispatcher;
}
Dispatcher怎了來的搞清楚了,我們接着就看下它是怎麼起到任務調度作用的。
1.2 Dispater簡介
在具體跟蹤分析上述提到的excuted(Call)
和equeue(AnsycCall)
之前,我們先來簡單看下Dispatcher這個類的成員變量,這有助於我們後面的流程分析。
public final class Dispatcher {
// 最大請求數目
private int maxRequests = 64;
// 統一個Host最大請求數目
private int maxRequestsPerHost = 5;
// 線程池 用於執行異步請求任務
private @Nullable ExecutorService executorService;
// 準備發起異步請求的雙向隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
// 正在發起異步請求的雙向隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
// 正在發起同步請求的雙向隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
……
}
這裏列出了幾個比較關鍵的字段。其中maxRequests
規定了同一個OkHttpClient
中可同時發起的最大請求數,maxRequestsPerHost
規定了統一Host中可同時發起的最大請求數。接着executorService
是一個線程池,最終我們的異步任務就是通過該線程池執行的。從這我們可以看到,並沒有在這裏直接進行初始化,OkHttp框架進行了一種懶加載策略的,在它的getter方法中可以看到它的創建:
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;
}
可以看到,該線程池的核心線程數爲0,保活60s,也就意味着空閒線程在等待60秒後如果還沒有使用就會被回收。以Integer.MAX_VALUE
爲最大線程數數量,我們也就可以認爲該線程池是沒有上界限制的,只要有合法請求,就會創建新的線程,這裏的合法請求是指沒有超過maxRequests
和maxRequestsPerHost
的限制。
剩下還有三個雙向隊列的屬性,註釋裏面已經說的比較清楚了,這裏就不再贅述了。
1.3 Dispatcher任務調度過程
有了上面的基礎,就可以進入任務調度過程分析了,這裏我們還是先看同步任務,這裏貼出RealCall.execute
方法
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// 加入隊列
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// 請求完成,移除隊列
client.dispatcher().finished(this);
}
}
直接跟進去看下Dispatcher.executed(ReallCall)
方法:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
非常簡單,就是將傳入的RealCall
加入了runningSyncCalls
這個隊列中。接着看上面的流程,當請求完成後最終會調用Dispatcher.finishd(ReallCall)
方法,我們進去看下:
void finished(RealCall call) {
finished(runningSyncCalls, call);
}
調用了它的重載方法:
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
// 移除隊列
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
// 這裏會取檢查執行異步請求
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
此時這裏的參數calls
就是runningSyncCalls
對象,所以這裏第一步就是將之前的Call
移除隊列。然後執行了promoteAndExecute();
這個方法,該方法如果返回fasle說明當前沒有請求任務了,所以後續做了判斷,如果沒有任務,idleCallback
也不爲null的話,就會回調該對象的run
。所以如果我們想在空閒的時候做一些處理,就可以設置IdleCallback
接口。接着進入promoteAndExecute();
方法裏面具體看下是怎麼處理的:
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
// 從readyAsyncCalls取出任務,加入executableCalls集合
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
// 從executableCalls裏面一次取出AsyncCall對象,並調用它的executeOn()方法
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
這個方法裏面就是從readyAsyncCalls
隊列中取出待執行的異步任務,調用異步任務的executeOn(executorService())方法。其中參數是之前所述的線程池對象——executorService
。到這裏就和上篇分析的異步請求流程接上了,這裏就不再往下分析了。
到這裏,同步調度過程就分析完了,可以看到,同步請求任務並沒真正由Dispatcher來調度,只是在其內部維護了一個雙向隊列而已。所以Dispatcher主要目的還是用來調度異步任務的,接下來看異步調度過程。
還是從ReallCall
的enqueue(Callback)
方法開始跟蹤:
@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));
比較簡單,通過我們傳入的Callback
對象構造了一個AsyncCall
,然後調用了Dispatcher
的enqueue(AsyncCall)
方法,跟進去:
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
就是將call加入到readyAsyncCalls
隊列中,然後調用了promoteAndExecute();
這個方法,前面已經分析過了,這裏不再贅述。通過上篇文章的分析,我們知道,異步請求,最終在AsyncCall對象中執行excute()
方法的時候,最終也會調用Dispatcher.finish(AsyncCall)
方法,最終會調用finished(Deque<T> calls, T call)
這個方法,這個方法前面已經分析又會去執行promoteAndExecute();
方法,直到任務隊列中沒有待執行的任務。
到這,整個整個任務的調度就分析完了。接下來我們接着看下上篇沒有詳細說明的getResponseWithInterceptorChain();
方法。
2 Interceptor調用鏈
上篇文章我們只是說了getResponseWithInterceptorChain();
會進入到我們的設置的Interceptor中,並最終返回請求的數據。具體是怎麼實現的呢?我們跟進去看下:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//添加開發者應用層自定義的Interceptor
interceptors.addAll(client.interceptors());
//這個Interceptor是處理請求失敗的重試,重定向
interceptors.add(retryAndFollowUpInterceptor);
//這個Interceptor就是OkHttp框架自動是添加一些額外的支持
//(如:使用篇裏面說到的gzip壓縮支持就是在這裏面處理)
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//這個Interceptor的職責是判斷緩存是否存在,讀取緩存,更新緩存等等
interceptors.add(new CacheInterceptor(client.internalCache()));
//這個Interceptor的職責是建立客戶端和服務器的連接
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
//添加開發者自定義的網絡層攔截器
interceptors.addAll(client.networkInterceptors());
}
//一個包裹這request的chain
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//這裏就把chain傳遞到第一個Interceptor調用鏈中了
return chain.proceed(originalRequest);
}
這裏創建了一個ArrayList,讓後不斷的往這個list裏面添加Interceptor。至於每個Interceptor的作用,代碼註釋裏面已經說明了。最後通過RealInterceptorChain.proceed(Request)
方法進入了Interceptor的調用鏈
2.1.5 RealInterceptorChain.proceed()
接着上面的步驟,我們看下怎麼通過proceed()
方法就進入了Interceptor的調用鏈的。分析這個方法之前,先注意上面創建RealInterceptorChain
對象的初始化參數:
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
這裏傳入了我們的Interceptor的List集合和Request對象,同時初始化index=0(第五個參數)。
接着看下proceed()
方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
……
// 創建下一個Interceptor所需的RealInterceptorChain
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 獲取index所在的Interceptor,並調用該Interc的intercept(Chain)方法
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
……
return response;
}
這裏暫時只需要我們先把整個調用鏈的流程搞清楚,所以就只留下了關鍵代碼。其他內容,後續文章分析網絡請求的時候,還會詳細說明。
根據上面的描述我們知道當前這個Chain對象中的index=0,所以Interceptors.get(index)
是第一個Interceptor,緊接着又使用Request和index+1的索引構建了下一個Chain對象。然後進入第一個Interceptor的intercept(Chain)
方法,傳入的是新的Chain
對象。根據之前使用篇裏面學習的知識,我們知道在這個方法中,如果我們需要放行一般會調用chain.process(Request)
方法,這樣就會進入上面新創建的Chain
的process(Request)
會進入該方法裏面,此時index=1的,所以又會取出下一個Interceptor出來,以此類推,一層一層往下執行,最終就形成了Interceptor的調用鏈。
根據以上分析,我們可以畫出大致的調用鏈的圖如下:
這裏只做整體流程分析,具體每個Interceptor裏面做的事情就不做詳述了。最後一個CallServerInterceptor
的intercept(Chain)
方法裏面裏面是沒有調用chain.proceed()
方法的,直接從網絡獲取Response並返回了,又一層一層的返回到最上面getResponseWithInterceptorChain
方法中。最終就形成了一個“U”型結構的調用鏈。