原始碼分析OKHttp的執行過程
OKHttp
是目前Android
平臺主流的網路請求的基礎框架。因此我們有必要對其原始碼進行閱讀學習,瞭解其內部的原理、專案結構、以及請求的執行過程。
它的專案地址為:ofollow,noindex">github.com/square/okht…
0x00 簡單使用
先從一個簡單的官方示例來看,這是一個同步GET
請求
public class GetExample { //1.http客戶端 OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { //2.構造請求 Request request = new Request.Builder() .url(url) .build(); //3.執行請求,獲取響應資料 try (Response response = client.newCall(request).execute()) { return response.body().string(); } } public static void main(String[] args) throws IOException { GetExample example = new GetExample(); String response = example.run("https://raw.github.com/square/okhttp/master/README.md"); System.out.println(response); } } 複製程式碼
可以看出這個GET
請求操作是很簡單的。有幾個很重要的介面
-
OKHttpClient
: 它代表著http
客戶端 -
Request
:它封裝了請求物件,可以構造一個http
請求物件 -
Response
:封裝了響應結果 -
Call
:client.newCall
呼叫後生成一個請求執行物件Call
,它封裝了請求執行過程。
這幾個介面是程式員在使用OKHttp
庫中經常遇到的。
接下來將從這個示例開始閱讀OkHttp
的原始碼
0x01 Call.execute()
跟進原始碼後發現這個方法是在Call
中的介面
/** * A call is a request that has been prepared for execution. A call can be canceled. As this object * represents a single request/response pair (stream), it cannot be executed twice. */ public interface Call extends Cloneable { //... //同步執行請求 Response execute() throws IOException; //將請求加入佇列 void enqueue(Callback responseCallback); //... } 複製程式碼
從原始碼註釋知道,Call
是一個準備請求的執行物件,它可以被取消,代表一個 “請求/響應” 對,不能執行兩次。
RealCall
Call
的實現類是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 = ture
,否則丟擲異常 -
呼叫了回撥函式
callStart
-
okhttp
客戶端呼叫dispatcher
將執行請求物件 -
呼叫了
getResponseWithInterceptorChain
方法獲取到響應資料Response
,這個方法很重要,後面會繼續跟進 -
然後是對請求失敗的回撥
callFailed
-
最後還是使用
dispather
物件呼叫finished
方法,完成請求
這裡的邏輯還是比較清晰的,出現兩個重要的方法
dispatcher.execute getResponseWithInterceptorChain
接下來分別看這兩個方法
0x02 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<>(); //... synchronized void enqueue(AsyncCall call) { if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); } } /** Used by {@code Call#execute} to signal it is in-flight. */ synchronized void executed(RealCall call) { runningSyncCalls.add(call); } /** Used by {@code AsyncCall#run} to signal completion. */ void finished(AsyncCall call) { finished(runningAsyncCalls, call, true); } /** 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(); } } } 複製程式碼
可以看出Dispatcher
是一個排程器,它內部有一個執行緒池executorService
,還有三個佇列,分別代表同步請求進行佇列、非同步請求等待佇列、非同步請求執行佇列。
我們發現呼叫execute方法時就是將Call物件加入到同步請求進行佇列runningSyncCalls中,而呼叫finished 方法則是將Call請求從佇列中移除
0x03 getResponseWithInterceptorChain
現在在回到RealCall
原始碼中,這個方法可以說是OkHttp
最關鍵的部分了
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors());//新增程式設計師自定義的的攔截器 interceptors.add(retryAndFollowUpInterceptor);//重試和重定向攔截器 interceptors.add(new BridgeInterceptor(client.cookieJar()));//處理cookie的攔截器 interceptors.add(new CacheInterceptor(client.internalCache()));//處理快取的攔截器 interceptors.add(new ConnectInterceptor(client));//負責連線的攔截器 if (!forSocket/">WebSocket) { interceptors.addAll(client.networkInterceptors());//新增程式設計師自定義的network攔截器 } interceptors.add(new CallServerInterceptor(forWebSocket));//呼叫服務攔截器 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); } 複製程式碼
在添加了一系列的攔截器之後,又構造了一個攔截器責任鏈,這個RealInterceptorChain
包含了所有的攔截器物件。然後呼叫chain.proceed
方法開始執行請求,這時就到了RealInterceptorChain
這個類中。
0x04 RealInterceptorChain
@Override public Response proceed(Request request) throws IOException { return proceed(request, streamAllocation, httpCodec, connection); } public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); calls++; //省略無關程式碼... //1. 執行攔截器責任鏈中的下一個攔截器 RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout); //2. 獲取當前的攔截器 Interceptor interceptor = interceptors.get(index); //3. 執行攔截,並返回響應 Response response = interceptor.intercept(next); //省略... return response; } 複製程式碼
可以看到,在proceed
方法,又構造了RealInterceptorChain
並且呼叫了interceptor.intercept
方法,
而這個方法中又會呼叫next.proceed
方法,直至返回response
。這個過程有點像遞迴呼叫。
0x05 Interceptor
攔截器,它是一個介面,內部還有一個Chain
介面
public interface Interceptor { Response intercept(Chain chain) throws IOException; interface Chain { Request request(); Response proceed(Request request) throws IOException; /** * Returns the connection the request will be executed on. This is only available in the chains * of network interceptors; for application interceptors this is always null. */ @Nullable Connection connection(); Call call(); int connectTimeoutMillis(); Chain withConnectTimeout(int timeout, TimeUnit unit); int readTimeoutMillis(); Chain withReadTimeout(int timeout, TimeUnit unit); int writeTimeoutMillis(); Chain withWriteTimeout(int timeout, TimeUnit unit); } } 複製程式碼
所有的攔截器都需要實現這個介面。
0x06 非同步的情況
public final class AsynchronousGet { private final OkHttpClient client = new OkHttpClient(); public void run() throws Exception { Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); //呼叫enqueue方法,並設定回撥介面 client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { e.printStackTrace(); } @Override public void onResponse(Call call, Response response) throws IOException { //這裡獲取到響應結果資料 } }); } 複製程式碼
然後我們再看RealCall
中的enqueue
方法
@Override public void enqueue(Callback responseCallback) { synchronized (this) { if (executed) throw new IllegalStateException("Already Executed"); executed = true; } captureCallStackTrace(); eventListener.callStart(this); //最終執行了dispatcher的enqueue方法 client.dispatcher().enqueue(new AsyncCall(responseCallback)); } 複製程式碼
其實是執行了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
中通過執行緒池來執行AsyncCall
物件,因此跟進到AsyncCall
中的execute
方法
@Override protected void execute() { boolean signalledCallback = false; try { //最終還是呼叫了getResponseWithInterceptorChain()!!! 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); } } 複製程式碼
發現最終還是執行了getResponseWithInterceptorChain
,因此不管是同步還是非同步、最終的流程還是一樣。
0x07 總結
-
OKHttpClient
這是一個http
客戶端。構建很簡單,可以使用無參建構函式。其內部是通過Builder
物件進行構建的。也可以通過其內部靜態類Builder
來構建,然後通過builder
設定OkHttpClient
構造引數。
-
Request
請求物件。其內部也是使用Builder
模式封裝了構造的過程,通過Builder
使用鏈式呼叫也是目前很多開源庫中常見的模式。
-
Response
響應結果。客戶端執行後返回響應結果,通過Response
可以很方便的獲取到響應資料。
-
Call
請求執行。可以執行同步或者非同步的請求,分別將請求傳送到dispatcher
-
Dispatcher
排程器。其內部有一個執行緒池,並維護了三個佇列:同步進行請求佇列、非同步請求等待佇列、非同步請求進行佇列。
還有兩個重要的方法execute
和enqueue
方法,分別代表同步、非同步的方法。這兩個方法的最終的執行流程都是一樣的
-
Interceptor
攔截器。攔截器在OKHttpClient
中使是用責任鏈模式來實現的。Okhttp
中的關鍵的流程是通過攔截器責任鏈來完成的。