簡介
OkHttp 是一款用于 Android 和 Java 的網絡請求庫,也是目前 Android 中最火的一個網絡庫。OkHttp 有很多的優點:
- 在 HTTP/2 上允許對同一個 host 的請求共同一個 socket
- 連接池的使用減少請求延遲(如果 HTTP/2 不支持)
- 透明的 GZIP 壓縮減少數據量大小
- 響應的緩存避免重復的網絡請求
之前寫過一篇 Retrofit 源碼解析,Retrofit 底層其實就是用的 OkHttp 去請求網絡。本文分析 OKHttp 的源碼,主要是針對一次網絡請求的基本流程,源碼基于 OKHttp-3.8.0
基本用法
下面是 OkHttp 的使用示例:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
// 同步
Response response = client.newCall(request).execute();
// 異步
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
首先是創建一個 OkHttpClient
對象,其實用過的應該知道可以用 new OkHttpClient.Builder().build()
的方式來配置 OkHttpClient
的一些參數。有了 OkHttpClient
之后,下面是創建一個 Request
對象,這個對象也是通過 Builder 模式來生成,其中可以配置一些與這條請求相關的參數,其中 url 是必不可少的。在發送請求的時候,需要生成一個 Call
對象,Call
代表了一個即將被執行的請求。如果是同步請求,調用 execute
方法。異步則調用 enqueue
,并設定一個回調對象 Callback
。
下面就一步步分析發送一條網絡請求的基本流程。
OkHttpClient、Request 及 Call 的創建
OkHttpClient 的創建采用了 Builder 模式,可以配置 Interceptor、Cache 等。可以設置的參數很多,其中部分參數如下:
final Dispatcher dispatcher; // 請求的分發器
final @Nullable Proxy proxy; // 代理
final List<Protocol> protocols; // http協議
final List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors;
final List<Interceptor> networkInterceptors;
final EventListener.Factory eventListenerFactory;
final ProxySelector proxySelector;
final CookieJar cookieJar;
final @Nullable Cache cache;
Request 與 OkHttpClient 的創建類似,也是用了 Buidler 模式,但是其參數要少很多:
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
...
}
參數的含義都很明確,即使 Http 協議的url、header、method 以及 body 部分。變量 tag
用于標識一條 Request
,可用于發送后取消這條請求。
client.newCall(request)
生成一個 Call 對象。Call 實際上是一個接口,它封裝了 Request,并且用于發起實際的網絡請求。下面是 Call 的全部代碼:
public interface Call extends Cloneable {
Request request();
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
Call clone();
interface Factory {
Call newCall(Request request);
}
}
其中包含了與網絡請求相關的操作,包括發起、取消等??匆幌?OkHttpClient
是如何創建 Call
的:
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
從代碼可以看到,實際上是創建了一個 RealCall
對象,它也是 Call 的唯一一個實現類。
有了 RealCall
對象后,就可以發起網絡請求了,可以是同步請求(execute
)或者是異步請求(enqueue
)。異步請求涉及到 Dispatcher
,先從相對簡單的同步請求開始分析。
同步請求
調用 RealCall#execute()
即是發起同步請求,代碼如下:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
首先判斷這條請求是不是已經執行過,如果是則會拋出異常(一條請求只能執行一次,重復執行可以調用 Call#clone()
)。接著執行了 client.dispatcher().executed(this)
,這行代碼是把當前的 Call 加入到 Dispatcher 的一個隊列中,這個暫時可以忽略,后面會分析 Dispatcher。
下面一行 Response result = getResponseWithInterceptorChain()
是關鍵,在 getResponseWithInterceptorChain
中真正執行了網絡請求并獲得 Response 并返回。(下一小節具體分析其中的邏輯)
最后在 finally 中調用 Dispatcher 的 finished
,從隊列中移除這條請求。
攔截器 Interceptor
getResponseWithInterceptorChain
的代碼如下:
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()));
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);
return chain.proceed(originalRequest);
}
可以看到,其中創建了一個 List 用于添加 Interceptor。首先添加的是 client 中的 interceptors,也就是在創建 OkHttpClient
對象時自定義的 interceptors,然后依次添加 retryAndFollowUpInterceptor
(重試及重定向)、BridgeInterceptor
(請求參數的添加)、CacheInterceptor
(緩存)、ConnectInterceptor
(開始連接)、用戶自定義的 networkinterceptors
及 CallServerInterceptor
(發送參數并讀取響應)。從這里可以知道,OkHttp 默認添加了好幾個 interceptor 用于完成不同的功能。
在研究各個 interceptor 之前,需要考慮一下如何讓這些攔截器一個接著一個的執行?繼續看上面的代碼,在添加了各種 interceptors 之后,創建了一個 RealInterceptorChain
對象。(它的構造函數需要的參數很多,并且這些參數涉及到連接池、請求數據的發送等。由于這篇文章主要分析 OkHttp 的基本流程,所以暫時略過這部分)RealInterceptorChain
是接口 Chain
的實現類,Chain
是 鏈 的意思,其作用是把各個 Interceptor 串起來依次執行。在獲得了 RealInterceptorChain
之后調用其 proceed
方法,看名字就能知道是讓 Request
請求繼續執行。
下面具體分析 RealInterceptorChain
,它有如下的成員變量:
private final List<Interceptor> interceptors; // 攔截器
private final StreamAllocation streamAllocation; // 流管理器
private final HttpCodec httpCodec; // http流,發送請求數據并讀取響應數據
private final RealConnection connection; // scoket的連接
private final int index; // 當前攔截器的索引
private final Request request; // 當前的請求
private int calls; // chain 的 proceed 調用次數的記錄
其中 streamAllocation
、httpCodec
和 connection
都與 socket 連接有關,后續文章再分析??匆幌?proceed
方法:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
// 如果已經有了一個流,確保即將到來的 request 是用它
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
// 如果已經有了一個流,確保這是對 call 唯一的調用
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request); // (1)
Interceptor interceptor = interceptors.get(index); // (2)
Response response = interceptor.intercept(next); // (3)
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
剛開始做了一些連接方面的判斷,需要關注的是標了(1)、(2)、(3)的幾行,主要做了以下操作:
- 創建新的
RealInterceptorChain
,其中index
加1用于標識當前的攔截器 - 通過
index
獲取當前的攔截器 - 調用下一個攔截器的
intercept
方法,并把上面生成的新的 RealInterceptorChain 對象next
傳進去
由之前的 getResponseWithInterceptorChain
方法可以知道,當前 RealInterceptorChain
的 interceptors 的第一個是 RetryAndFollowUpInterceptor
,下面是其 intercept
的代碼:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
// 調用 chain 的 proceed
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
... // 省略部分代碼,主要是錯誤重試以及重定向
}
}
這個 Interceptor 主要用于出錯重試以及重定向的邏輯,其中省略了部分代碼。在這個方法當中要關注的是再次調用了 chain
的 proceed
方法,這里的 chain
是之前新創建的 next
對象。相當于說通過調用 Chain#proceed()
將網絡請求推向下一個攔截器(proceed
中會獲取下一個 Interceptor 并調用其 intercept
方法),并且得到 response 對象,而下一個攔截器也是類似的操作。于是,多個 interceptors 就通過這種方式串起來依次執行,并且前一個 Interceptor 可以得到后一個 Interceptor 執行后的 response 從而進行處理。
通過不同的 Interceptor,OkHttp 實現了不同的功能。各個 Inercept 職責分明又不會互相耦合,并且可以非常方便的添加 Interceptor,這是 責任鏈 模式的體現,非常優雅的設計?,F在可以發現 OkHttp 中的攔截器的調用過程如下圖所示:

異步請求
相比于同步請求,異步請求主要是增加了 Dispatcher 的處理。Dispatcher 是請求的分發器,它有一下的成員變量:
private int maxRequests = 64; // 最大連接數
private int maxRequestsPerHost = 5; // 單個 host 最大連接數
private @Nullable Runnable idleCallback; // 空閑時的回調
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; // 線程池
/** Ready async calls in the order they'll be run. */
// 準備執行的異步 Call 的隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執行的的異步 Call 的隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執行的同步 Call 的隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
在 Dispatcher 中,默認支持的最大并發連接數是64,每個 host 最多可以有5個并發請求。
下面看一下線程池 executorService
的創建。線程池會在兩個地方創建,分別是 Dispatcher 的構造函數或者是 executorService
方法中(如果調用了默認的構造函數):
// 默認構造函數沒有創建
public Dispatcher() {
}
// 自定義線程池
public Dispatcher(ExecutorService executorService) {
this.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;
}
Dispatcher 支持自定義的線程池,否則會默認創建一個。在生成 OkHttpClient
對象時,默認調用的是 Dispatcher 無參的構造方法。這個默認線程池通過 new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false))
創建,看上去類似于一個 CachedThreadPool,沒有常駐的 core 線程,空閑線程60秒后自動關閉。
enqueue
每個 Call 被添加到某一個隊列,如果是同步請求添加到 runningSyncCalls
中:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
異步請求添加的邏輯如下:
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
具體步驟是:
- 判斷是否超出總共的最大連接數以及單個 host 的最大連接數
- 如果沒有則添加到
runningAsyncCalls
并且提交到線程池執行 - 否則添加到
readyAsyncCalls
等待后續執行
需要注意的是異步請求的 Call 不是原始的 Call,而是被包裝為 AsyncCall
:
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl());
this.responseCallback = responseCallback;
}
...
@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 {
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
AsyncCall
繼承自 NamedRunnable
,它其實就是一個為線程設置了名字的 Runnable,在其 Run 中調用 execute
,所以 AsyncCall
的主要邏輯都寫在 execute
中??梢钥吹阶罱K還是調用了 getResponseWithInterceptorChain
方法,所以后續執行網絡請求的邏輯是一樣的。在獲得 response 之后,就可以調用 responseCallback
返回最終的信息。
finished
在上面的代碼中,finally 里面執行了 client.dispatcher().finished(this)
,在同步請求 RealCall#execute()
中也有類似的一行代碼。finished
的作用是讓 Dispatcher 從隊列中移除已完成的 Call,對于異步請求還會從 readyAsyncCalls
中取出等待中的請求提交給線程池。下面是具體代碼:
/** 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();
}
}
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();
// 找到一個等待隊列中的 Call,符合連接數要求時加入 runningAsyncCalls 并提交給線程池執行。
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
有兩個重載的 finished
方法均調用了另一個 pirvate 的 finished
,區別在于這個 finished
的最后一個參數 promoteCalls
。對于同步請求(參數為 RealCall
) promoteCalls
為 false
,而異步請求(參數為 AsyncCall
) promoteCalls
為 true
。 pirvate 的 finished
主要是從隊列中移除 Call,異步請求會執行 promoteCalls
。promoteCalls
里面主要是從 readyAsyncCalls
取出一個 Call,如果滿足最大連接數的要求,則把這個 Call 加入 runningAsyncCalls
并提交給線程池執行。
通過 runningAsyncCalls
和 readyAsyncCalls
,Dispatcher 實現了異步請求的調度執行。這里比較巧妙的方式是在 finally 中去執行 readyAsyncCalls
中的請求,避免了 wait/notity 的方式,避免了代碼的復雜性。
總結
OkHttp 的基本執行流程如下圖所示:

主要是以下步驟:
-
OkHttpClient
調用newCall
創建RealCall
對象,Call
封裝了Request
,代表一條即將執行的請求。 - 根據同步還是異步請求分別調用
RealCall
的execute
或enqueue
方法,將Call
加入Dispatcher
的相應隊列中。最終,同步或異步請求都會調用getResponseWithInterceptorChain
。 - 在
getResponseWithInterceptorChain
中,OkHttp 添加用戶自定義以及默認的 inceptors,并用一個Chain
管理并依次執行每個 Interceptor。 - 每個 Interceptor 調用
Chain#proceed()
將請求發送給下一級的 Inceptor,并能通過這個方法獲得下一級 Interceptor 的 Response。所以上圖所示,Request 一級級地往下傳遞,而獲取了網絡的 Response 之后一級級地往上傳遞。
OkHttp中一條網絡請求的基本流程就是這樣,下一篇文章介紹 OkHttp 如何建立連接:OkHttp 源碼解析(二):建立連接。