這篇文章主要梳理一下 OkHttp 的請求流程,對 OkHttp 的實現原理有個整體的把握,再深入細節的實現會更加容易。
建議將 OkHttp 的原始碼下載下來,使用 IDEA 編輯器可以直接開啟閱讀。我這邊也將最新版的原始碼下載下來,進行了註釋,有需要的可以直接從 這裡 下載檢視。
基本使用
我們先看一下 OkHttp 的基本使用。
// 1、建立 Request
Request request = new Request.Builder()
.get()
.url("xxx")
.build();
// 2、建立 OKHttpClient
OkHttpClient client = new OkHttpClient();
// 3、建立 Call
Call call = client.newCall(request);
try {
// 4、同步請求
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
// 5、非同步請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
複製程式碼
上面的程式碼中,首先構建一個請求 Request 和一個客戶端 OkHttpClient,然後 OkHttpClient 物件根據 request 呼叫 newCall
方法建立 Call 物件,再呼叫 execute
或者 enqueue
方法進行同步或者非同步請求。
接下來我們看一看關鍵類和關鍵流程的具體實現。
Request
Request 類封裝了一次請求需要傳遞給服務端的引數:請求 method 如 GET/POST 等、一些 header、RequestBody 等等。
Request 類未對外提供 public 的建構函式,所以構建一個 Request 例項需要使用構造者模式構建。
Request(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers.build();
this.body = builder.body;
this.tags = Util.immutableMap(builder.tags);
}
複製程式碼
OkHttpClient
OkHttpClient 支援兩種構造方式。
一種是預設的構造方式:
OkHttpClient client = new OkHttpClient();
複製程式碼
看一下建構函式:
public OkHttpClient() {
this(new Builder());
}
複製程式碼
這裡 OkHttpClient 內部預設配置了一些引數。
OkHttpClient(Builder builder) {...}
複製程式碼
另一種是通過 Builder 配置引數,最後通過 build
方法構建一個 OkHttpClient 物件。
OkHttpClient client = new OkHttpClient.Builder().build();
public OkHttpClient build() {
return new OkHttpClient(this); // 這裡的 this 是 Builder 例項
}
複製程式碼
我們看一下 OkHttpClient 可配置哪些引數:
final Dispatcher dispatcher; // 排程器
final @Nullable
Proxy proxy; // 代理
final List<Protocol> protocols; // 協議
final List<ConnectionSpec> connectionSpecs; // 傳輸層版本和連線協議
final List<Interceptor> interceptors; // 攔截器
final List<Interceptor> networkInterceptors; // 網路攔截器
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector; // 代理選擇器
final CookieJar cookieJar; // cookie
final @Nullable
Cache cache; // 快取
final @Nullable
InternalCache internalCache; // 內部快取
final SocketFactory socketFactory; // socket 工廠
final SSLSocketFactory sslSocketFactory; // 安全套接層 socket 工廠,用於 https
final CertificateChainCleaner certificateChainCleaner; // 驗證確認響應證照 適用 HTTPS 請求連線的主機名
final HostnameVerifier hostnameVerifier; // 主機名字驗證
final CertificatePinner certificatePinner; // 證照鏈
final Authenticator proxyAuthenticator; // 代理身份驗證
final Authenticator authenticator; // 本地身份驗證
final ConnectionPool connectionPool; // 連線池
final Dns dns; // 域名
final boolean followSslRedirects; // 安全套接層重定向
final boolean followRedirects; // 本地重定向
final boolean retryOnConnectionFailure; // 重試連線失敗
final int callTimeout;
final int connectTimeout;
final int readTimeout;
final int writeTimeout;
final int pingInterval;
複製程式碼
Call
Call 是一個介面,是請求的抽象描述,具體實現類是 RealCall,通過Call.Factory 建立。
public interface Call extends Cloneable {
// 返回當前請求
Request request();
// 同步請求方法
Response execute() throws IOException;
// 非同步請求方法
void enqueue(Callback responseCallback);
// 取消請求
void cancel();
// 請求是否在執行(當execute()或者enqueue(Callback responseCallback)執行後該方法返回true)
boolean isExecuted();
// 請求是否被取消
boolean isCanceled();
Timeout timeout();
// 建立一個新的一模一樣的請求
Call clone();
interface Factory {
Call newCall(Request request);
}
}
複製程式碼
OkHttpClient 實現了 Call.Factory,負責根據 Request 建立新的 Call:
Call call = client.newCall(request);
複製程式碼
看一下 newCall
方法。
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
複製程式碼
這裡我們發現實際上呼叫了 RealCall 的靜態方法 newRealCall
, 不難猜測 這個方法就是建立 Call 物件。
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 的 execute
方法。
@Override public Response execute() throws IOException {
synchronized (this) {
// 每個 call 只能執行一次
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
// 請求開始, 將自己加入到runningSyncCalls佇列中
client.dispatcher().executed(this);
// 通過一系列攔截器請求處理和響應處理得到最終的返回結果
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
// 請求完成, 將其從runningSyncCalls佇列中移除
client.dispatcher().finished(this);
}
}
複製程式碼
這裡主要做了這幾件事:
- 檢測這個 call 是否已經執行了,保證每個 call 只能執行一次。
- 通知 dispatcher 已經進入執行狀態,將 call 加入到 runningSyncCalls 佇列中。
- 呼叫
getResponseWithInterceptorChain()
函式獲取 HTTP 返回結果。 - 最後還要通知
dispatcher
自己已經執行完畢,將 call 從 runningSyncCalls 佇列中移除。
這裡涉及到了 Dispatcher 這個類,我們在非同步請求這一節中再介紹。
真正發出網路請求以及解析返回結果的是在 getResponseWithInterceptorChain
方法中進行的。
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
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);
複製程式碼
getResponseWithInterceptorChain
方法的程式碼量並不多,但是卻完成了所有的請求處理過程。
這裡先是建立了一個 Interceptor 的集合,然後將各類 interceptor 全部加入到集合中,包含以下 interceptor:
- interceptors:配置 OkHttpClient 時設定的 inteceptors
- RetryAndFollowUpInterceptor:負責失敗重試以及重定向
- BridgeInterceptor:負責把使用者構造的請求轉換為傳送到伺服器的請求、把伺服器返回的響應轉換為使用者友好的響應
- CacheInterceptor:負責讀取快取直接返回、更新快取
- ConnectInterceptor:負責和伺服器建立連線
- networkInterceptors:配置 OkHttpClient 時設定的 networkInterceptors
- CallServerInterceptor:負責向伺服器傳送請求資料、從伺服器讀取響應資料
新增完攔截器後,建立了一個 RealInterceptorChain 物件,將集合 interceptors 和 index(數值0)傳入。接著呼叫其 proceed
方法進行請求的處理,我們來看 proceed
方法。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
...
// 建立下一個RealInterceptorChain,將index+1(下一個攔截器索引)傳入
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
// 獲取當前的攔截器
Interceptor interceptor = interceptors.get(index);
// 通過Interceptor的intercept方法進行處理
Response response = interceptor.intercept(next);
...
return response;
}
複製程式碼
我們來看一些關鍵程式碼:
RealInterceptorChain 的 proceed
方法先建立 RealInterceptorChain 的物件,將集合 interceptors 和 index + 1 傳入。從前面的分析知道,初始 index 為 0。
然後獲取當前 index 位置上的 Interceptor,將建立的 RealInterceptorChain 物件 next 傳入到當前攔截器的 intercept
方法中,intercept
方法內部會呼叫 next 的 proceed 方法,一直遞迴下去,最終完成一次網路請求。
所以每個 Interceptor 主要做兩件事情:
- 攔截上一層攔截器封裝好的 Request,然後自身對這個 Request 進行處理,處理後向下傳遞。
- 接收下一層攔截器傳遞回來的 Response,然後自身對 Response 進行處理,返回給上一層。
非同步請求
非同步請求呼叫的是 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);
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
複製程式碼
與同步請求一樣,非同步請求也涉及了一個重要的參與者 Dispatcher,它的作用是:控制每一個 Call 的執行順序和生命週期。它內部維護了三個佇列:
- readyAsyncCalls:等待的非同步請求佇列
- runningAsyncCalls:正在執行的非同步請求佇列
- runningSyncCalls:正在執行的同步請求佇列
對於同步請求,由於它是即時執行的, Dispatcher 只需要執行前請求前儲存到 runningSyncCalls,請求結束後從 runningSyncCalls 中移除即可。
對於非同步請求,Dispatcher 是通過啟動 ExcuteService 執行,執行緒池的最大併發量 64,非同步請求先放置在 readyAsyncCalls,可以執行時放到 runningAsyncCalls 中,執行結束從runningAsyncCalls 中移除。
我們看一下具體實現細節,下面是 Dispatcher 的 enqueue
方法,先將 AsyncCall 新增到 readyAsyncCalls。
void enqueue(AsyncCall call) {
// 將AsyncCall加入到準備非同步呼叫的佇列中
synchronized (this) {
readyAsyncCalls.add(call);
}
promoteAndExecute();
}
複製程式碼
再看 promoteAndExecute
方法:
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (runningCallsForHost(asyncCall) >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
executableCalls.add(asyncCall);
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
複製程式碼
這裡主要的工作有:
- 從準備非同步請求的佇列中取出可以執行的請求(正在執行的非同步請求不得超過64,同一個host下的非同步請求不得超過5個),加入到
executableCalls
列表中。 - 迴圈
executableCalls
取出請求 AsyncCall 物件,呼叫其executeOn
方法。
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
複製程式碼
可以看到 executeOn
方法的引數傳遞的是 ExecutorService 執行緒池物件,方法中呼叫了執行緒池的 execute
方法,所以 AsyncCall 應該是實現了 Runnable 介面,我們看看它的 run
方法是怎樣的。
AsyncCall 繼承自 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();
}
複製程式碼
所以當執行緒池執行 execute
方法會走到 NamedRunnable 的 run
方法,run
方法中又呼叫了 抽象方法 execute
,我們直接看 AsyncCall 的 execute
方法。
@Override
protected void execute() {
boolean signalledCallback = false;
timeout.enter();
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) {
e = timeoutExit(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
方法。
這樣看來,同步請求和非同步請求的原理是一樣的,都是在 getResponseWithInterceptorChain()
函式中通過 Interceptor 鏈條來實現的網路請求邏輯。
總結
以上便是 Okhttp 整個請求與響應的具體流程,OkHttp 流程圖如下。
簡述 OkHttp 的請求流程:
- OkHttpClient 實現了 Call.Fctory,負責為 Request 建立 Call。
- RealCall 是 Call 的具體實現,它的非同步請求是通過 Dispatcher 排程器利用 ExcutorService 實現,而最終進行網路請求時和同步請求一樣,都是通過
getResponseWithInterceptorChain
方法實現。 getResponseWithInterceptorChain
方法中採用了責任鏈模式,每一個攔截器各司其職,主要做兩件事。- 攔截上一層攔截器封裝好的 Request,然後自身對這個 Request 進行處理,處理後向下傳遞。
- 接收下一層攔截器傳遞回來的 Response,然後自身對 Response 進行處理,返回給上一層。