一、前言
在 OkHttp 知識梳理(1) - OkHttp 原始碼解析之入門 中,介紹了OkHttp
的簡單使用及同步請求的實現流程,今天這篇文章,我們來一起學習一下非同步請求的內部實現原理及執行緒排程。
首先,讓我們回顧一下非同步請求的實現方式:
private void startAsyncRequest() {
//以下三步和同步請求的步驟相同。
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(URL).build();
Call call = client.newCall(request);
//區別在於拿到 RealCall 物件之後的處理方式。
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
String result = response.body().string();
//返回結果給主執行緒。
Message message = mMainHandler.obtainMessage(MSG_UPDATE_UI, result);
mMainHandler.sendMessage(message);
}
});
}
複製程式碼
可以看到,對於非同步請求而言,前面三步和同步請求是相同的,區別在於發起請求時,同步請求使用的是call.execute()
,只有當整個請求完成時才會從.execute()
函式返回。
而對於非同步請求來說,.enqueue(Callback)
方法只要呼叫完就立即返回了,當網路請求返回之後會回撥Callback
的onResponse/onFailure
方法,並且這兩個回撥方法是在子執行緒執行的,這也是非同步請求和同步請求之間最主要的差別。
下面我們就來分析一下非同步請求的內部實現邏輯。
二、非同步請求原始碼解析
對於前面三步的內部實現不再重複說明了,大家可以檢視 OkHttp 知識梳理(1) - OkHttp 原始碼解析之入門 中的分析。最終我們會得到一個RealCall
例項,它代表了一個執行的任務。接下來看enqueue
內部做了什麼。
public void enqueue(Callback responseCallback) {
//首先判斷該物件是否曾經被執行過。
synchronized(this) {
if(this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
//捕獲堆疊資訊。
this.captureCallStackTrace();
//通知監聽者請求開始了。
this.eventListener.callStart(this);
//呼叫排程器的 enqueue 方法。
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
複製程式碼
這裡,我們又見到了熟悉的dispatcher()
類,它enqueue
的實現為:
private int maxRequests = 64;
private int maxRequestsPerHost = 5;
//執行任務的執行緒池。
private ExecutorService executorService;
//等待被執行的非同步請求任務佇列。
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque();
//正在被執行的非同步請求任務佇列。
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque();
public synchronized ExecutorService executorService() {
if(this.executorService == null) {
this.executorService = new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
}
return this.executorService;
}
synchronized void enqueue(AsyncCall call) {
//如果當前正在請求的數量小於 64,並且對於同一 host 的請求小於 5,才發起請求。
if(this.runningAsyncCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
//將該任務加入到正在請求的佇列當中。
this.runningAsyncCalls.add(call);
//通過執行緒池執行任務。
this.executorService().execute(call);
//否則加入到等待佇列當中。
} else {
this.readyAsyncCalls.add(call);
}
}
複製程式碼
Dispatcher
的enqueue
首先會判斷:如果當前正在請求的數量小於64
,並且對於同一host
的請求小於5
,才發起請求。發起請求之前會將RealCall
加入到runningAsyncCalls
佇列當中,並通過ThreadPoolExecutor
來執行該請求,
2.1 通過執行緒池執行任務
ThreadPoolExecutor
是Java
提供的執行緒池,在 多執行緒知識梳理(6) - 執行緒池四部曲之 ThreadPoolExecutor 中我們已經介紹過它,這裡根據它的引數配置可以看出,它對應於CachedThreadPool
,該執行緒池的特點是 執行緒池大小無界,適用於執行很多的短期非同步任務的程式或者是負載較輕的伺服器。
- 等待佇列使用的是
SynchonousQueue
,它的 每個插入操作都必須等待另一個執行緒的移除操作,對於執行緒池而言,也就是說:在新增任務到等待佇列時,必須要有一個空閒執行緒正在嘗試從等待佇列獲取任務,才有可能新增成功。 - 因此,當一個任務被新增進入執行緒池時,會有以下兩種情況:
- 如果當前有空閒執行緒正在嘗試從等待佇列中獲取任務,那麼這個 任務將會被交給這個空閒執行緒 進行處理
- 如果當前沒有空閒執行緒嘗試從等待佇列中獲取任務,那麼將會 建立一個新執行緒來執行任務
- 由於設定了等待超時時間,某個執行緒在
60s
內都無法獲取到新的任務將會被銷燬。
執行緒池的execute
函式接收Runnable
的介面實現類作為引數,在該任務被執行時將會呼叫它的run()
方法,這上面的AsyncCall
也是一樣的道理,它繼承了NamedRunnable
抽象類,而NamedRunnable
又實現了Runnable
介面,當NamedRunnable
的run()
方法被回撥時,會呼叫AsyncCall
的execute()
方法。
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
//responseCallback 就是呼叫 call.enqueue 方法時傳入的回撥。
AsyncCall(Callback responseCallback) {
super("OkHttp %s", new Object[]{RealCall.this.redactedUrl()});
this.responseCallback = responseCallback;
}
//該函式是在子執行緒當中執行的。
protected void execute() {
boolean signalledCallback = false;
try {
//和同步請求的邏輯相同。
Response response = RealCall.this.getResponseWithInterceptorChain();
if(RealCall.this.retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
this.responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException var6) {
if(signalledCallback) {
Platform.get().log(4, "Callback failure for " + RealCall.this.toLoggableString(), var6);
} else {
RealCall.this.eventListener.callFailed(RealCall.this, var6);
this.responseCallback.onFailure(RealCall.this, var6);
}
} finally {
//呼叫 Dispatcher 的 finished 方法
RealCall.this.client.dispatcher().finished(this);
}
}
}
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(this.name);
try {
//呼叫子類的 execute() 方法。
this.execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
protected abstract void execute();
}
複製程式碼
execute()
方法最終是在 子執行緒當中執行的,這裡我們看到了熟悉的一句話:
Response response = RealCall.this.getResponseWithInterceptorChain();
複製程式碼
這裡面就是進行請求的核心邏輯,我們在 OkHttp 知識梳理(1) - OkHttp 原始碼解析之入門 中的3.4
節中已經介紹過了,這裡會通過一系列的攔截器進行處理,重試請求、快取處理和網路請求都是在裡面完成的,最終得到返回的Response
,並根據情況回撥最開始傳入的Callback
的onResponse/onFailure
方法。
2.2 任務執行完後的處理
當回撥完之後,最終會呼叫Dispatcher
的finished
方法:
void finished(AsyncCall call) {
//如果是非同步請求,那麼最後一個引數為 true。
this.finished(this.runningAsyncCalls, call, true);
}
void finished(RealCall call) {
//如果是同步請求,那麼最後一個引數為 false。
this.finished(this.runningSyncCalls, call, false);
}
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) {
this.promoteCalls();
}
runningCallsCount = this.runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
private void promoteCalls() {
if (this.runningAsyncCalls.size() < this.maxRequests) {
if (!this.readyAsyncCalls.isEmpty()) {
Iterator i = this.readyAsyncCalls.iterator();
do {
if(!i.hasNext()) {
return;
}
AsyncCall call = (AsyncCall)i.next();
if (this.runningCallsForHost(call) < this.maxRequestsPerHost) {
i.remove();
//找到了等待佇列中符合執行的條件的任務,那麼就執行它。
this.runningAsyncCalls.add(call);
this.executorService().execute(call);
}
} while(this.runningAsyncCalls.size() < this.maxRequests);
}
}
}
複製程式碼
這裡和同步請求相同,都會走到finished
方法當中,區別在於最後一次引數是true
,而同步請求是false
,也就是說會呼叫到promoteCalls
方法中,promoteCalls
的作用為:在最開始時,如果不滿足執行條件,那麼任務將會被加入到等待佇列readyAsyncCalls
中,那麼當一個任務執行完之後,就需要去等待佇列中尋找符合執行條件的任務,並將它加入到任務佇列中執行,之後的邏輯和前面的相同。
promoteCalls
函式除了在一個非同步請求執行完畢後會呼叫,當我們改變最大請求數量和對於同一個host
的最大請求數量時,也會觸發該查詢過程。
//改變了最大請求數量。
public synchronized void setMaxRequests(int maxRequests) {
if(maxRequests < 1) {
throw new IllegalArgumentException("max < 1: " + maxRequests);
} else {
this.maxRequests = maxRequests;
this.promoteCalls();
}
}
//改變了同一個 Host 的最大請求數量。
public synchronized void setMaxRequestsPerHost(int maxRequestsPerHost) {
if(maxRequestsPerHost < 1) {
throw new IllegalArgumentException("max < 1: " + maxRequestsPerHost);
} else {
this.maxRequestsPerHost = maxRequestsPerHost;
this.promoteCalls();
}
}
複製程式碼
三、小結
以上就是對於非同步請求方式的原始碼分析,由此我們可以總結出OkHttp
對於非同步請求的排程方式:
- 通過兩個列表對非同步請求任務進行管理,
runningAsyncCalls
中存放的是正在執行任務的列表,readyAsyncCalls
中則是等待被執行的任務。 - 當一個任務執行完畢後,會去
readyAsyncCalls
查詢下一個可以被執行的任務。 - 任務的執行是通過執行緒池
ThreadPoolExecutor
在子執行緒中來完成的,由它來負責正在執行任務的排程,內部的實現原理如 多執行緒知識梳理(6) - 執行緒池四部曲之 ThreadPoolExecutor 所分析。 - 真正進行網路請求的核心程式碼在
AsyncCall
的execute()
函式中,這裡會通過一系列的攔截器進行處理,重試請求、快取處理和網路請求都是在裡面完成的,最終得到返回的Response
,並根據情況回撥最開始傳入的Callback
的onResponse/onFailure
方法。 - 對於非同步請求而言,
Callback
的onResponse/onFailed
是在子執行緒當中執行的,因此如果要在其中執行更新UI
的操作,那麼需要通知主執行緒來更新。
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- Android 面試文件分享:www.jianshu.com/p/8456fe6b2…