OkHTTP的任務調度(Dispatcher)
簡單描述
我們都知道同步和異步在外部方法呼叫區別是非常明顯的,同步使用execute(),異步使用enqueue,在這之前我們可以考慮下兩個問題
- okhttp如何實作同步異步請求?
主要通過dispatcher類來完成的,發送的同步/異步請求都會在dispatcher中管理其狀態
- 到底什么是dispatcher?
dispatcher的作用為維護請求的狀態,并維護一個執行緒池,用于執行請求
dispatcher原始碼實作
回答完上述問題相信就會有個非常直觀的解釋,Dispatcher就是維護我們的請求狀態(包括了同步和異步),所以每當有網路請求,都會通過Call封裝的dispatcher推送到就緒請求佇列,而OkhTTP相對與別的網路框架來說,就優于在內部維護了一個執行緒池,能夠更高效的
定義佇列
這幾個佇列的定義前面已經提及了,這里有必要重新回顧一下,因為比較重要
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
-
首先看readyAsyncCall,它是就緒狀態的異步請求佇列,當我們整個異步請求不滿足條件的時候,就會進入到就緒異步請求佇列中,進行快取,當條件再滿足的時候就會放到正在執行的異步請求佇列中進行執行
-
然后是runningAyncCalls,表示的是正在執行的異步請求佇列,看下它的注釋,需要注意的是它包含了已經取消但沒有執行完的請求
還有個比較重要的執行緒池executorService,因為dispatcher正是通過這個執行緒池來維護了整個異步請求,進行高效的網路請求
說到這里,可以思考下,為什么異步請求需要兩個佇列呢?
其實這里可以理解成為生產者/消費者模型,Dispatcher作為生產者,ExecutorService作為消費者池,既然是消費者/生產者模型,那必然需要兩個佇列來存放執行異步請求和等待的異步請求,一個Deque快取,一個Deque正在執行的任務;
看下同步和異步請求中Dispatcher的使用
同步請求(call.execute())
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
- 這里我們可以看到同步請求dispatcher做的非常簡單,就是集合的添加操作
- 每當有新的同步請求的時候,dispatcher會直接將它加入到同步的正在執行的佇列中
異步請求(call.enqueue())
異步請求對于dispatcher的使用要比同步請求復雜一些
- 前面我們已經請求了異步請求也是通過dispatcher的enqueue()方法進行的,可以看下它的原始碼
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
-
首先滿足兩個條件(一個是正在運行異步請求佇列個數要小于最大可請求個數,一個是當前請求佇列Host下的請求數一定要小于我們給它設定的最大值),這個時候就可以把封裝好的AsyncCall添加到我們正在執行的異步佇列當中,然后交給執行緒池執行,這里的執行緒池會負責我們自動創建,銷毀,以及一些執行緒的管理.
-
如果不滿足上述條件,直接添加到就緒異步等待佇列當中,進行快取等待操作.
-
其實異步請求也不算復雜,但是它通過一個dispatcher來封裝好了請求管理,幫助我們做了相應的作業
-
接下來我們來看下executorService()執行緒池的執行
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,因為空閑一段時間后,需要把我們所用到的執行緒全部銷毀
- 第二個引數表示最大的執行緒池,我們把它設定為整型的最大值,這是因為當我們的請求過來的時候,可以無限的擴充執行緒最大值,當然這是理論上這么說,但是okhttp的請求受到maxRequest變數的限制,所以也不是* 無限的創建執行緒池的
- 第三個引數表示的是當我們的執行緒數大于核心執行緒數的時候,那么空閑執行緒最大的存活時間就是60秒
說到這里,可能很多小伙伴不太理解為什么執行緒池要設定這三個引數,到底意味著什么,這里我簡單舉個例子
在我們實際運行中,我們需要開啟20個并發請求,這時候執行緒池也會創建20個執行緒,那么當作業完成之后,執行緒池就會在60秒之后相繼關閉所有無用的執行緒,這就是它執行緒池所設定這三個引數的意義所在
- 綜上所訴,Call執行完肯定需要在runningAsyncCalls佇列中移除這個執行緒的,那么就需要思考一個問題了:那么readySsyncCalls佇列中的執行緒在什么時候才會執行呢?
還是要回到原始碼當中,可以看下enqueue方法傳入的封裝請求的AsyncCall,毋庸置疑,前面我們已經知道它是個Runnable,這樣的話它執行的時候一定是放在執行緒池中的,可以追溯下AsyncCall這個執行緒執行方法
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
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 {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
dispatcher里的finish方法
上述代碼前面部分已經在之前的文章中解釋過來,這里我站在dispatcher的角度來看的話,可以看到有個finally,無論有沒有例外它都會執行,而里面主要是執行dispatcher里的finish方法
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
第一步就是呼叫了calls.remove(),正是去洗掉我們正在執行的異步請求
第二步就是promoteCalls(),這個方法其實就是調整我們的任務佇列,無論是異步還是同步的請求佇列它都是執行緒不安全;所以我們呼叫這個方法的時候都是在synchronized代碼塊中執行
判斷正在執行的請求數量是否等于0和idleCallback不允許為空,滿足這個條件后才繼續執行
看下finished方法中的promoteCalls()實作
我們上面知道了finish做了主要的操作,看下promoteCalls()方法可以發現,原來就是在這里對我們的快取等待異步請求的佇列進行調度的
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
可以看到它會先對原來的異步請求佇列進行遍歷,呼叫它的remove()洗掉最后一個元素之后,然后加入到正在執行的異步請求佇列當中,這就是promoteCalls方法的作用
尾言
至此,以上就是我對于Okhttp中的任務調度Dispatcher做出的總結,接下來我將會分析Okhttp中的攔截器,作為okhttp核心的部分,也是okhttp中提供的一種
強大機制
最后小編分享一份,我在學習提升時,從網上收集整理了一些 Android 開發相關的學習檔案、面試題、Android 核心筆記等等檔案,希望能幫助到大家學習提升,如有需要參考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 訪問查閱,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/294009.html
標籤:其他

