OkHttp(二) - 請求流程分析
上一篇文章簡要的介紹了OkHttp的API簡單使用,通過建立了OkHttpClient和Request這些物件就能對遠端請求建立連線,獲取資料。本篇文章將對OkHttp的請求流程做更進一步的深入分析,從原始碼的角度來看看它的請求流程是具體怎麼執行的。
請求方式
請求流程
OkHttp提供了兩種請求方法,分別是同步和非同步。其實兩種方式唯一的區別就在於,非同步方式是將我們的請求放入了一個執行緒池來執行的,其具體的底層請求實現機制都是一樣的。這裡先說一下大概的請求流程,以便在看後續的程式碼分析過程中能保持清晰的思路。
我們的請求都是通過一個OkHttpClient的物件來發起的,這個物件是個入口類,儲存了使用者的配置資訊和一些上下文環境資訊。Call是一個介面,可以抽象成使用者發起的請求呼叫,具體的實現類是RealCall這個物件,Requset就是具體的請求,儲存了你要請求的地址,ip,埠等資訊。
客戶端OkHttpClient發起呼叫請求,首先建立一個RealCall物件,然後根據使用者是選擇同步還是非同步方式來執行不同流程。如果是同步方式,則直接執行,如果是非同步方式,則將請求放入執行緒池來執行,最後通過一個攔截器鏈來完成具體的請求過程並獲取響應。這個攔截器鏈是整個OkHttp框架的核心,我會在後面詳細地介紹,這裡你可以就把它理解為一個執行具體請求的物件即可。用一個圖來描述吧,下圖就是請求流程的過程圖:
下面就深入程式碼來看看兩種不同的執行方式是怎麼實現的同步請求
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url("http://www.baidu.com" ).build();
Response response = client.newCall(request).execute()
複製程式碼
從OkHpptClient的入口newCall方法說起:
- 執行newCall,建立一個具體的請求呼叫物件RealCall
@Override public Call newCall(Request request) {
//建立一個具體的請求呼叫物件RealCall
return new RealCall(this,request,false /* for web socket */);
}
複製程式碼
- RealCall通過excute方法執行具體的請求
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
//將當前的請求加入runningSyncCalls佇列
client.dispatcher().executed(this);
//執行請求獲取響應
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
複製程式碼
在執行請求前會將當前的call加入一個叫做runningSyncCalls的佇列中,用於請求呼叫過程中的狀態管理和跟蹤處理,RealCall共有三個佇列:
-
非同步請求準備佇列,當非同步請求數量達到上限,會將請求儲存於此佇列中,待後續處理readyAsyncCalls 複製程式碼
-
當前正在執行的非同步請求佇列,儲存當前正在執行的請求runningAsyncCalls 複製程式碼
-
當前正在處理的同步請求佇列runningSyncCalls 複製程式碼
- 呼叫攔截器鏈完成請求處理
Response result = getResponseWithInterceptorChain(); 複製程式碼
上面這段程式碼是整個OkHttp框架中最核心的一句,它內部實現是通過一系列的攔截器組成了攔截器鏈,來完成請求的具體請求過程,包括重試,請求體處理,響應頭處理,響應體處理等邏輯。這裡我們不對它進行展開說明,你只需知道我們的請求是通過一個攔截器鏈來請求和獲取響應即可。上面的流程時序圖如下所示:
非同步請求
呼叫Call物件的excute方法執行同步請求,呼叫enqueue(Callback responseCallback)就會執行非同步請求,該方法需要傳入一個響應回撥介面,用於響應請求執行成功或執行失敗時的操作。
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
//標誌狀態
executed = true;
}
captureCallStackTrace();
//將請求加入佇列,並通過執行緒池來執行
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
複製程式碼
- 請求入隊 通過dispatch物件,將請求加入佇列
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
//如果已達到當前最大的處理請求數,則排隊等待
readyAsyncCalls.add(call);
}
}
複製程式碼
首先會判斷是否達到了請求上限,這裡的請求上限包含兩種:一、總的最大併發請求數;二、每個host繫結的最大併發請求數。如果兩個都沒有達到上限則會直接加入執行緒池進行處理。再來看看建立執行緒池的程式碼
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;
}
複製程式碼
這裡的執行緒池沒有核心執行緒,也就是說當沒有請求時不會有空閒的執行緒消耗資源,但是最大執行緒數為Integer.MAX_VALUE,似乎與我們平時的一些規範是衝突的。但還記得前面剛說過的請求上限嗎,其實這個上限數就限制了最大能建立的執行緒數,所以不用擔心。
- 如果沒有達到請求上限,呼叫執行緒池執行請求
executorService().execute(call)
複製程式碼
執行的call對應的實現類為AsyncCall,實際上它繼承了Runable()介面,在這個物件的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 {
responseCallback.onFailure(RealCall.this,e);
}
} finally {
//完成請求處理,從runningSyncCalls佇列中移除當前請求物件,從readyAsyncCalls中出隊請求,加入runningSyncCalls,並呼叫執行緒池執行
client.dispatcher().finished(this);
}
}
}
複製程式碼
具體執行請求的方法與同步請求方式中一樣,都是呼叫攔截器鏈來處理的
Response response = getResponseWithInterceptorChain();
複製程式碼
-
如果請求達到請求上限,則加入等待佇列,這個在前面第一步中請求進入佇列時已經說了,參考前面介紹
-
每執行完一個請求,從等待佇列中按順序出隊,進入執行緒池執行。在ASyncalCall物件的process方法中,最後都會執行finally方法體,呼叫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;
}
複製程式碼
具體看看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.
}
}
複製程式碼
總結
至此,同步請求和非同步請求的流程就分析完了,從中我們可以看出還是比較簡單,但是請求的具體執行執行流程是怎麼實現的,也就是說怎麼在攔截器處理鏈中處理的,這個是框架最核心的部分,我們還沒有具體分析,在後面的篇幅中我們將重點介紹。