前言
本篇源碼分析介紹的是同步請求和異步請求的執行流程以及排程器的相關知識。如果需要學習攔截器鍊相關知識的,可以直接跳過本篇直接到下一篇進行學習。
基本使用方法
首先要明确一點,
okhttp
使用的是 Builder 模式來進行我們網絡請求的各種參數設定的,接下來我們先舉個簡單的例子來看看
okhttp
的同步請求和異步請求是如何使用的。我們首先來看看同步請求的例子:
同步請求舉例
/**
* 同步請求的簡單舉例
*/
public static void SyncRequest(){
new Thread(new Runnable() {
@Override
public void run() {
// 1. 建構 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(8, TimeUnit.SECONDS)
.connectTimeout(8, TimeUnit.SECONDS)
.build();
// 2. 建構 Request 對象
Request request = new Request.Builder()
.get()
.url("https://www.baidu.com")
.build();
try {
// 3. 通過 newCall 方法建立 Call 對象
Call call = client.newCall(request);
//4. 通過調用 call 的 execute 方法執行同步請求
Response response = call.execute();
Log.d(TAG, response.body().string());
}catch (IOException e){
e.printStackTrace();
}
}
}).start();
}
這裡我們隻簡要地進行一個設定,主要是說明使用步驟。
- 首先,我們要構造出一個
對象,它可以說是我們整個流程的一個核心,我們通過 Builder 模式對它進行參數設定,如讀寫逾時、連接配接逾時時間和緩存設定等,這裡我隻簡單地對讀逾時和連接配接逾時進行了設定。OkHttpClient
- 其次,我們需要構造出
對象,它用于設定我們的請求參數,同樣它也是通過 Builder 模式進行建立的。Request
- 建構完
對象後,我們就可以調用Request
的OkHttpClient
方法,這個方法會将newCall
對象封裝成一個Request
類型的對象。Call
- 最後就是通過調用
的Call
方法來執行同步請求了,我們這裡通過一個execute
類型的變量來接收請求得到的資料。Response
在這裡我們需要注意一點的是,我們的同步請求是放在子線程中去執行的,具體原因很簡單:同步請求在沒有得到響應的時候,會阻塞線程,而我們的主線程是不允許有耗時操作的,是以我們在這裡需要開辟一個子線程來執行這個耗時操作。
講完了同步請求的流程步驟,接下來我們來看看異步請求的例子。
異步請求舉例
/**
* 異步請求的簡單舉例
*/
public static void AsyncRequest(){
// 1. 建構 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(8, TimeUnit.SECONDS)
.connectTimeout(8, TimeUnit.SECONDS)
.build();
// 2. 建構 Request 對象
final Request request = new Request.Builder()
.get()
.url("https://www.baidu.com")
.build();
// 3. 通過 newCall 方法建立 Call 對象
Call call = client.newCall(request);
// 4. 通過調用 call 的 enqueue 方法執行異步請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d(TAG, e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.d(TAG, response.body().string());
}
});
}
可以看到,異步請求和同步請求的設定其實有非常多的相似之處,它們的唯一不同點在于第4步的調用方法上,同步方法調用的是
execute
,而異步調用的則是
enqueue
方法。
enqueue
方法會接收一個
Callback
對象,這個對象會回調兩個關鍵方法:
onFailure
和
onResponse
。
onFailure
會在我們請求失敗的時候進行回調,而
onResponse
方法則是在得到響應後進行回調。
這裡同樣需要注意一點,所謂的異步,也就是不會阻塞我們的線程的意思,是以它實際上是在内部自己開辟了一個子線程來接收回調方法的,是以我們的
onFaliure
方法和
onResponse
方法都是位于我們的子線程中,在我們需要執行 UI 的相關操作時,應當先在方法内部切換回主線程進行操作。
接下來我們通過一張圖來對同步和異步的使用做一個簡單的總結:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLxkEVNl3ZU1ENNpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0QTM0ATOzUTM4ITMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
說完了
okhttp
兩種基本的使用方法,接下來我們便要從執行流程來一步步分析
okhttp
的源碼了。
源碼分析
本着先易後難的原則,筆者會首先對同步請求的執行流程進行分析,然後再對異步請求的執行流程進行分析,由于
okhttp
的源碼中涉及的重點如排程器器和攔截器等都是非常大的一塊知識,是以筆者會将執行的流程順序進行梳理,然後在梳理完畢之後再介紹排程器器和攔截器等的知識點。接下來我們來看看同步請求的執行流程。
同步請求的執行流程
通過上面的講解,我們知道了,在同步請求的時候,我們首先會通過
OkHttpClient.Builder()
來對
OkhttpClient
對象進行構造,我們首先來看看
Builder
裡面是什麼:
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
的一個靜态内部類,可以看到它的構造方法初始化了非常非常多的成員變量,我們可以通過調用相應的方法來設定這些成員變量的值。然後通過調用
build
方法,就可以傳回一個構造完畢的
OkHttpClient
的對象了,
build
的源碼如下所示:
public OkHttpClient build() {
return new OkHttpClient(this);
}
由于這個方法是
Builder
類中的方法,是以
this
所指的毫無疑問就是我們構造好的
Builder
對象了,通過傳入這個構造好的對象,我們就可以構造出我們所需的
OkhttpClient
對象了。
好了,在了解完
OkHttpClient
對象是如何構造出來之後,第二步就是構造
Request
對象了,它也是使用的 Builder 模式建構對象的,是以我們也來看看它的
Builder
裡面有什麼:
public Builder() {
this.method = "GET";
this.headers = new Headers.Builder();
}
可以看到在它的内部的
Builder
構造方法中,預設的請求方法是
get
請求,然後還會建立一個請求頭。最後也是通過
build
方法構造出我們需要的
Request
對象。
構造完
Request
對象後,接下來就到了我們的第三部,将
Request
對象封裝成
Call
對象。它是通過調用
OkHttpClient
的
newCall
方法實作的,我們來看看這個方法裡面做了什麼:
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
在這裡我們可以看到,這個方法傳回的是
RealCall
的
newRealCall
方法,在這裡需要先說明,
Call
是一個接口,它的實作類就是
RealCall
,我們接下來來看看
RealCall
的
newRealCall
方法做了什麼吧!
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
可以看到這個方法構造了一個
RealCall
對象,然後對這個對象設定了事件監聽,我們繼續往下看看
RealCall
的構造方法做了什麼:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
}
這個構造方法會将我們之前構造好的
OkHttpClient
對象和
Request
對象儲存起來,然後我們關鍵看到代碼第5行,這是一個重試和失敗重定向攔截器,我們後面會對
okhttp
的攔截器鍊機制進行分析,我們在這裡知道對它進行了初始化就可以了。
在知道
newCall
方法最終傳回的是一個
RealCall
對象之後,我們的同步請求就剩下最後一個調用方法
execute
了,這也是同步請求的核心方法。它的源碼如下所示:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
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);
}
}
在這個方法中,首先會在同步塊中對
executed
進行判斷,它主要是用于判斷我們目前的網絡請求是否被執行過了,因為一個網絡請求隻能被執行一次,如果我們的網絡請求已經被執行過,就會抛出異常。
接下來我們看到代碼第9行,這裡我們調用了
OkHttpClient
的
dispatcher
方法,我們來看看這個方法裡面做了什麼:
public Dispatcher dispatcher() {
return dispatcher;
}
可以看到,它就是簡單地傳回了我們的在
OkHttpClient
中的
Dispatcher
對象,它是一個排程器,用于控制并發的請求,我們的請求排程都是通過這個對象來完成的,它的源碼分析我們會留在後面。在得到
Dispatcher
對象後,我們接下來就是調用它的
executed
方法,它的源碼如下所示:
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
可以看到這是一個同步方法,它會将我們的
RealCall
對象通過
add
方法添加到
runningSyncCalls
這個對象當中,我們來看看它是什麼類型的對象:
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
可以看到這個對象是一個用于存放
RealCall
的隊列,通過注釋我們可以得知,當我們有産生同步請求時,就會将請求放置在這個隊列中。分析到這裡,我們知道了,同步請求的
executed
會首先将我們的請求添加到
runningSyncCalls
這個隊列中。接下來讓我們繼續回到
executed
方法往下看,我們接着建立了一個
Response
對象,通過
getResponseWithInterceptorChain
方法來接收我們的請求響應,
getResponseWithInterceptorChain
是我們
okhttp
分析的另一個重要的核心,我們同樣留在後面再做分析,這裡我們隻需要知道通過這個方法我們能夠擷取響應資料就可以了。在取得響應後,最後我們一定會執行
Dispatcher
的
finished
方法,
finished
方法的源碼如下所示:
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
可以看到它調用的是另一個
finished
方法,我們繼續看它的源碼:
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();
}
}
在這個方法中,我們看到它内部的同步塊,在代碼第5行,首先判斷能否移除這次已經執行結束的同步請求,如果不能就會抛出異常。接下來的
if
語句
promoteCalls
變量由于我們傳進來的是
false
,是以不會執行
promoteCalls
方法。跳過這個方法繼續往下看,在代碼第7行,我們會通過
runningCallsCount
計算現在正在執行的請求數,我們可以看看這段代碼的源碼:
public synchronized int runningCallsCount() {
return runningAsyncCalls.size() + runningSyncCalls.size();
}
可以看到它傳回的是
runningAsuncCalls
和
runningSyncCalls
這兩個隊列的大小之和,這兩個隊列分别存放着正在執行的異步和同步請求,是以通過傳回它們的大小也就能知道現在到底有多少請求正在執行了。
分析完這個方法後我們繼續回到
finished
方法的第11行,如果我們正在執行的請求數為0,也就是不存在請求了并且
idleCallback
不為空的話,就會執行
idleCallback
的
run
方法。
至此,我們的同步請求流程就分析完了,可以看到我們的同步請求流程還是比較簡單的,我們來對同步請求的執行流程做一個小總結:
- 同步請求首先構造出
對象和OkHttpClient
對象,設定好相應的參數。它們都是通過 Builder 模式進行構造的,其中Request
對象的預設請求方法是Request
。get
- 緊接着通過
的OkhttpClient
方法将newCall
對象封裝成Request
對象,Call
是一個接口,它的實作類是Call
。RealCall
- 最後便是調用
的Call
方法了,實際上也就是調用了execute
的RealCall
方法,這個方法會首先判斷目前請求是否被執行過,如果已經被執行過就會抛出異常,緊接着會執行排程器execute
的Dispatcher
方法,将executed
對象添加到同步請求隊列Call
中。runningSyncCalls
- 擷取的響應資料
對象是通過Response
方法來獲得的。getResponseWithInterceptorChain
- 在執行完請求之後,無論是否抛出異常,都一定會執行
方法來将finished
隊列中已執行完畢的同步請求runningSyncCalls
對象移除。Call
我們接下來先來分析我們的異步請求的執行流程。
異步請求的執行流程
在我們前面的例子中,我們已經知道了,同步和異步的差別僅僅在第4步的調用方法上:同步調用的是
execute
方法,而異步則是調用
enqueue
方法,前3步二者都是一樣的,是以在這裡的異步請求執行流程我們就直接從
enqueue
方法講起。
Call
的
enqueue
方法需要傳入一個
Callback
對象,我們首先來看看這個參數是怎麼定義的:
public interface Callback {
void onFailure(Call call, IOException e);
void onResponse(Call call, Response response) throws IOException;
}
可以看到非常簡單的一個接口,定義了兩個方法,分别對應于我們請求失敗和請求成功的情況,這個接口的作用是回調。接下來我們就來看看
enqueue
方法的内部代碼,前面提到過了
Call
是個接口,是以
enqueue
方法的實作應該在它的實作類
RealCall
裡面,
RealCall
中的
enqueue
代碼如下所示:
@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));
}
可以看到它其實和我們同步請求調用的
execute
方法非常相似,都是首先在同步塊中判斷這個請求是否被執行過了,如果為
true
就會抛出異常。接下來就是它和
execute
方法的不同點了,我們看到代碼第8行,它通過
OkHttpClient
獲得
Dispatcher
對象,然後再通過
Dispatcher
對象調用
enqueue
方法。
Dispatcher
的
enqueue
方法接收一個
AsyncCall
類型的參數,在
AsyncCall
中會對我們的
Callback
對象進行包裝,我們首先來看看這個
AsyncCall
是何方神聖:
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
......
}
可以看到它内部維護了一個
Callback
對象,我們傳進來的
Callback
在這裡被儲存了起來,我們還看到它是繼承自
NamedRunnable
的,我們來繼續看看
NamedRunnable
裡面是什麼:
public abstract class NamedRunnable implements Runnable {
protected final String name;
public NamedRunnable(String format, Object... args) {
this.name = Util.format(format, args);
}
@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
是一個抽象類,它是實作了
Runnable
的。在它的
run
方法中,調用了
execute
方法,而
execute
方法我們在代碼第18行可以看到是一個抽象方法,因為它是在
run
方法中被執行的,是以它就是在子線程中被執行的,那麼接下來我們便回到
NamedRunnable
的父類
AsyncCall
方法中看看父類的實作方法
execute
裡面做了什麼:
@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);
}
}
我們首先看到代碼第4行,在這裡我們依然是通過
getResponseWithInterceptorChain
方法來獲得我們的響應資料
Response
,
getResponseWithInterceptorChain
方法這裡後面做講解,我們現在隻需要知道通過這個方法我們能擷取我們想要的資料就行了。接下來看到第5行的
if
判斷,這裡我們通過
retryAndFollowUpInterceptor
做了一個判斷來決定回調哪個方法。
retryAndFollowUpInterceptor
前面我們已經說過了是一個重試和失敗重定向攔截器,它的
isCanceled
方法如果傳回
true
,說明攔截器鍊被關閉了,那麼我們也就無法獲得響應了,關于它的知識我們會留在後面一篇部落格進行講解。是以如果這個
if
判斷為
true
,那麼我們就會回調我們
Callback
對象的
onFailure
方法,否則就會回調我們
Callback
對象的
onResponse
方法。而在代碼第18行我們可以看到,如果抛出異常,也會回調
onFailure
方法。
最後看到
finally
塊中調用的
finished
方法,既然是放在
finally
中的,那就一定會被執行,我們來看看它裡面做了什麼:
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
可以看到它調用的是另一個
finished
方法,這個方法和我們在同步請求中調用的是同一個
finished
方法,不同點是第一個參數異步請求傳進來的是
runnigAsyncCalls
隊列,并且第三個參數傳進來的是
true
。我們再次來看看這個方法:
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call))sho throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
首先在同步塊中判斷能否移除這個已經執行完畢的異步
Call
請求,不能移除就會抛出異常。緊接着由于
promoteCalls
參數我們傳進來的值是
true
,那麼在異步中我們就會執行
promoteCalls
方法了,這個方法涉及到了我們
Dispatcher
排程器的内容,我們同樣是留在後面做講解,它的作用是在任務執行完之後,手動清除掉線程池中的緩存區,并把等待中的請求添加到執行隊列中去,我們先記住這一點就可以了。接下來就是通過判斷正在執行的請求數來決定是否調用
idleCallback
的
run
方法了,這個在前面講解同步請求的時候已經介紹過了,是以也就不再贅述了。
總結一下
AsyncCall
的
execute
方法,它的作用就是在新開辟的子線程中,擷取響應,然後根據不同的情況來決定回調
Callback
的
onFailure
或者
onResponse
方法。這也印證了我們之前的注意事項,在
onFaliure
或
onResponse
方法中操作 UI 需要先調回主線程再進行操作。在執行完目前的異步請求之後,會調用
promoteCalls
方法手動清理線程池中的緩存區,并把等待中的請求添加到執行隊列中去。
分析完了
AsyncCall
的
execute
方法,我們的
AsyncCall
就分析到這裡了。
AsyncCall
的重點也就是它的
execute
方法,要記住它是運作在
run
方法中的。分析完了
Dispatcher
中的
enqueue
方法的參數
AsyncCall
,我們接下來就來看看
Dispatcher
的
enqueue
方法裡面做了什麼吧:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到這是一個同步方法,在這段代碼中,我們先來看看
runningAsyncCalls
和
readyAsyncCalls
的含義:
/** 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<>();
從注釋中我們可以得知,
readyAsyncCalls
是一個用于存放異步請求的等待隊列,而
runningAsyncCalls
則是一個用存放正在執行異步請求的隊列,這個正在執行也包括那些未執行完但被我們取消了的請求。知道了這些資訊,我們繼續回到
enqueue
方法,看到
enqueue
方法的第2行,在這個
if
判斷中,如果
runningAsyncCalls
的大小小于
maxRequests
并且
runningCallsForHost
方法的傳回值小于
maxRequestsPerHost
時,我們的
AsyncCall
對象就會被添加到
runningAsyncCalls
隊列中,否則
AsyncCall
對象就會被添加到
readyAsyncCalls
隊列中。
我們來分析這個
if
判斷,
maxRequests
的預設值為64,也就是說當異步請求正在執行的個數小于64的時候,第一個條件成立。而
maxRequestsPerHost
的 預設值為5,
runningCallsForHost
方法傳回目前請求的
host
共享的請求數,如果不超過5個,那麼第二個條件就成立了。也就是說,當正在執行的異步請求個數少于64個并且目前請求的
host
請求數不超過5個時,我們的
AsyncCall
對象就會被直接添加到
runningAsyncCalls
隊列中,否則就會添加到
readyAsyncCalls
隊列中。
在
if
條件成立後,我們除了将
AsyncCall
對象添加到
runningAsyncCalls
隊列中外,我們還執行了
executorService().execute(call)
語句,我們首先來看看
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;
}
可以看到這個方法傳回的是一個線程池,緊接着這個傳回的線程池就會調用它的
execute
方法來執行我們的
AsyncCall
對象的線程的
run
方法,我們之前分析過了
AsyncCall
方法的線程執行的
run
方法其實就是執行
AsyncCall
的
execute
方法,在它裡面會根據不同的情況回調我們
Callback
對象的
onFailure
方法或
onResponse
方法。是以千萬要記住一句話:
onFailure
和
onResponse
方法都是位于子線程中的方法,不要在裡面直接進行 UI 操作!
到這裡,我們的異步請求的執行流程分析就結束了。我們來對異步請求的執行流程做一個小總結:
- 首先異步請求調用的
方法内部會判斷這個請求是否被執行過了,如果是執行過的就會抛出異常,因為每個請求隻能被執行一次。enqueue
- 接着就會通過排程器來執行
的Dispatcher
方法,這個方法中我們會傳入enqueue
對象的包裝類Callback
的對象,然後通過判斷我們的異步請求應當添加到AsyncCall
隊列中直接執行還是runningAsyncCalls
隊列中進行等待,如果是直接執行,那麼就會線上程池中執行我們的請求。readyAsyncCalls
- 線上程池中執行請求實際上會調用到
中的AsyncCall
方法,這個方法會根據不同情況決定回調execute
對象的Callback
方法還是onFailure
方法,是以這兩個方法是在子線程中執行的,不要在裡面直接操作 UI !!!onResponse
- 在執行完請求之後,無論是否抛出異常,都一定會執行
方法來将finished
隊列中的請求對象移除。runningAsyncCalls
排程器 Dispatcher
我們在前面的源碼分析中,一直跳過的排程器部分,就要在這裡進行分析了。從前面的源碼中我們也能感受的到,無論是同步請求還是異步請求,在執行的方法(
execute
/
enqueue
)中總會通過擷取到的
Dispatcher
對象的
execute
或
enqueue
方法來執行我們的網絡請求。是以
okhttp
的請求管理,也就是通過
Dispatcher
對象來排程的了。首先我們來看到
Dispatcher
這個類的代碼:
public final class Dispatcher {
......
/** 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<>();
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
public Dispatcher() {
}
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;
}
......
}
可以看到,這個類的内部定義了一個線程池
ExecutorService
,還有3個隊列,分别是
readyAsyncCalls
、
runningAsyncCalls
和
runningSyncCalls
。這些我們在之前的分析中都有介紹過,在這裡我們再來對它們進行一次介紹,因為它們太重要了。
首先是線程池
ExecutorService
,它可以通過構造方法傳入我們自定義的線程池進行構造或者通過預設的線程池進行構造,從注釋中我們知道它是通過懶漢模式加載的,也就是用到它的時候它才會被執行個體化。它是通過
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;
}
這個方法在之前我們粗略地介紹了一下,我們在這裡仔細地看看它是如何構造出一個線程池的。在代碼第3行,可以看到建立了一個線程池,傳進的前4個參數分别為 0,
Integer.MAX_VALUE
,60 和
TimeUnit.SECONDS
。因為這裡涉及到的是線程池的知識,不懂的同學請自行搜尋相關知識進行學習,這裡不會進行細緻的介紹。
這個線程池的核心線程數為 0,最大線程數為
Integer
的最大值,也就是說理論上我們可以建立出無限多個線程,但事實上它會受到我們其它變量的限制,是以我們實際上無法建立出無限多的線程出來。
緊接着兩個參數設定的是非核心線程閑置的逾時時間,在這裡的設定是 60s。而我們的線程池中所有線程均為非核心線程,也就是說,當我們的線程線上程池中超過 60s 沒有被執行的時候,它就會被回收掉。
介紹完了線程池,我們接下來看到三個隊列:
- readyAsyncCalls:異步等待隊列,當異步請求無法添加到異步執行隊列中時,就會被添加到這個隊列中來。
- runningAsyncCalls:異步執行隊列,在這個隊列中的異步請求不但包括了正在執行的請求,也包括未執行完成但被取消了的請求。
- runningSyncCalls:同步執行隊列,在這個隊列中的同步請求不但包括了正在執行的請求,也包括未執行完成但被取消了的請求。
在簡單分析完 Dispatcher 這個類的幾個重要的成員之後,我們依然分為同步請求和異步請求的排程來進行分析。
同步請求的排程
我們在這裡就直接看到同步請求的
execute
方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
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);
}
}
這段代碼我們之前已經分析過了,接下來我們站在
Dispatcher
的角度來進行分析。我們直接看到代碼第9行,調用
Dispatcher
的
executed
方法,這個方法的代碼如下:
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
這個方法非常的簡單,就是直接将
Call
對象添加到了
runningSyncCalls
這個同步執行隊列中,表明它正在被執行,緊接着當執行結束之後,我們一定會調用到第17行
Dispatcher
的
finished
方法,我們重新來看看這個方法裡面幹了什麼:
/** Used by {@code Call#execute} to signal completion. */
void finished(RealCall call) {
finished(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) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
這個方法調用的就是下面的
finished
方法,在這個方法中,我們看到代碼第10行,我們會在這裡從
runningSyncCalls
隊列中移除我們的
Call
對象,因為它已經執行完了嘛。如果無法移除了,那麼它就會抛出異常。是以到這裡,我們就把同步請求中排程器對
Call
從添加到移除的流程介紹完了,接下來介紹更為複雜的異步請求排程過程。
異步請求的排程
在異步請求中,我們也是直接看到我們的
enqueue
方法:
@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));
}
我們直接看到代碼第8行,在這裡我們執行了
Dispatcher
的
enqueue
方法,它的代碼如下:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
這段代碼我們在之前同樣也進行過分析,我們在這裡重新在
Dispatcher
的角度進行分析。首先
if
判斷語句會判斷正在執行的異步請求線程數是否小于 64 以及目前請求的主機請求數是否小于 5,如果都滿足的話,那麼就會将請求直接添加到
runningAsyncCalls
隊列中,并且将請求交由線程池進行處理,否則就會被添加到
readyAsyncCalls
隊列中進行等待。線程池中處理的
run
方法實際上處理的就是
AsyncCall
的
execute
方法,這個方法的源碼如下:
@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
相關的部分,我們直接看到代碼第21行的
finally
塊中,在這裡的時候我們的請求已經執行完畢了,是以接下來就會調用
Dispatcher
的
finished
方法:
/** Used by {@code AsyncCall#run} to signal completion. */
void finished(AsyncCall call) {
finished(runningAsyncCalls, call, true);
}
它内部調用的
finished
和同步請求的調用的是同一個
finished
方法,差別是它的第一個參數傳入的是異步執行隊列
runningAsyncCalls
,第三個參數傳入的是
true
。我們再次看到
finished
方法:
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();
}
}
同樣的,在同步塊中,我們會先将執行完的
Call
移除,由于我們傳進的
promoteCalls
值為
true
,是以在異步請求中
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.
}
}
這個方法主要是用于将異步等待隊列中的請求排程到異步執行隊列中。在代碼第2行的
if
判斷中,首先判斷異步執行隊列
runningAsyncCalls
的數量是否超過了最大值,如果是就直接傳回,因為這意味着我們的異步執行隊列沒有空間留給我們等待中的請求了。接下來看到代碼第3行的
if
判斷,如果異步等待隊列為空,也是直接傳回,這也好了解,如果沒有等待中的請求了,也就不需要排程了。
接下來就到了代碼第5行的
for
循環了,這是整段代碼的關鍵。在這個
for
循環中,我們會周遊異步等待隊列
readyAsyncCalls
的請求,然後在執行隊列
runningAsyncCalls
有空閑位置的時候就會将等待隊列中的
Call
移除,然後添加到執行隊列中去。也就是在這裡,
okhttp
實作了對異步等待隊列中的請求的排程。對于異步請求的排程,到這裡也就分析完了。
在最後,我們對排程器做個小總結:
- 在同步請求中,排程器在同步請求執行前将其添加到同步隊列
中,在執行結束後從隊列中将它移除。runningSyncCalls
- 在異步請求中,排程器首先會判斷異步執行隊列
是否未滿以及請求的主機請求數是否未超過5個,如果這2個條件均滿足就會直接将請求添加到runningAsyncCalls
中并線上程池中執行這個請求,當請求執行結束後,排程器會通過runningAsyncCalls
方法将promoteCalls
隊列中的請求排程到readyAsyncCalls
中去執行。runningAsyncCalls
總結
本次源碼分析主要是對同步請求和異步請求的異同、執行流程和排程器
Dispatcher
做了一個分析,相對而言還是比較簡單的。對于本篇遺留下來的知識點攔截器鍊,會在後面的系列文章進行講解。祝大家學習愉快!有問題的話可以在下面評論區給我留言。