簡介
OkHttp 是一款用于 Android 和 Java 的網(wǎng)絡請求庫绘迁,也是目前 Android 中最火的一個網(wǎng)絡庫。OkHttp 有很多的優(yōu)點:
- 在 HTTP/2 上允許對同一個 host 的請求共同一個 socket
- 連接池的使用減少請求延遲(如果 HTTP/2 不支持)
- 透明的 GZIP 壓縮減少數(shù)據(jù)量大小
- 響應的緩存避免重復的網(wǎng)絡請求
之前寫過一篇 Retrofit 源碼解析卒密,Retrofit 底層其實就是用的 OkHttp 去請求網(wǎng)絡缀台。本文分析 OKHttp 的源碼哮奇,主要是針對一次網(wǎng)絡請求的基本流程睛约,源碼基于 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 {
}
});
首先是創(chuàng)建一個 OkHttpClient
對象,其實用過的應該知道可以用 new OkHttpClient.Builder().build()
的方式來配置 OkHttpClient
的一些參數(shù)哲身。有了 OkHttpClient
之后辩涝,下面是創(chuàng)建一個 Request
對象,這個對象也是通過 Builder 模式來生成勘天,其中可以配置一些與這條請求相關(guān)的參數(shù)怔揩,其中 url 是必不可少的。在發(fā)送請求的時候脯丝,需要生成一個 Call
對象商膊,Call
代表了一個即將被執(zhí)行的請求。如果是同步請求宠进,調(diào)用 execute
方法晕拆。異步則調(diào)用 enqueue
,并設定一個回調(diào)對象 Callback
材蹬。
下面就一步步分析發(fā)送一條網(wǎng)絡請求的基本流程实幕。
OkHttpClient、Request 及 Call 的創(chuàng)建
OkHttpClient 的創(chuàng)建采用了 Builder 模式堤器,可以配置 Interceptor茬缩、Cache 等『鹁桑可以設置的參數(shù)很多凰锡,其中部分參數(shù)如下:
final Dispatcher dispatcher; // 請求的分發(fā)器
final @Nullable Proxy proxy; // 代理
final List<Protocol> protocols; // http協(xié)議
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 的創(chuàng)建類似,也是用了 Buidler 模式圈暗,但是其參數(shù)要少很多:
public final class Request {
final HttpUrl url;
final String method;
final Headers headers;
final @Nullable RequestBody body;
final Object tag;
...
}
參數(shù)的含義都很明確掂为,即使 Http 協(xié)議的url、header员串、method 以及 body 部分勇哗。變量 tag
用于標識一條 Request
,可用于發(fā)送后取消這條請求寸齐。
client.newCall(request)
生成一個 Call 對象欲诺。Call 實際上是一個接口渺鹦,它封裝了 Request,并且用于發(fā)起實際的網(wǎng)絡請求毅厚。下面是 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);
}
}
其中包含了與網(wǎng)絡請求相關(guān)的操作塞颁,包括發(fā)起祠锣、取消等伴网。看一下 OkHttpClient
是如何創(chuàng)建 Call
的:
@Override public Call newCall(Request request) {
return new RealCall(this, request, false /* for web socket */);
}
從代碼可以看到拳氢,實際上是創(chuàng)建了一個 RealCall
對象馋评,它也是 Call 的唯一一個實現(xiàn)類留特。
有了 RealCall
對象后蜕青,就可以發(fā)起網(wǎng)絡請求了右核,可以是同步請求(execute
)或者是異步請求(enqueue
)贺喝。異步請求涉及到 Dispatcher
躏鱼,先從相對簡單的同步請求開始分析染苛。
同步請求
調(diào)用 RealCall#execute()
即是發(fā)起同步請求茶行,代碼如下:
@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);
}
}
首先判斷這條請求是不是已經(jīng)執(zhí)行過,如果是則會拋出異常(一條請求只能執(zhí)行一次怔鳖,重復執(zhí)行可以調(diào)用 Call#clone()
)结执。接著執(zhí)行了 client.dispatcher().executed(this)
献幔,這行代碼是把當前的 Call 加入到 Dispatcher 的一個隊列中蜡感,這個暫時可以忽略郑兴,后面會分析 Dispatcher情连。
下面一行 Response result = getResponseWithInterceptorChain()
是關(guān)鍵虫几,在 getResponseWithInterceptorChain
中真正執(zhí)行了網(wǎng)絡請求并獲得 Response 并返回挽拔。(下一小節(jié)具體分析其中的邏輯)
最后在 finally 中調(diào)用 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);
}
可以看到辨绊,其中創(chuàng)建了一個 List 用于添加 Interceptor匹表。首先添加的是 client 中的 interceptors默蚌,也就是在創(chuàng)建 OkHttpClient
對象時自定義的 interceptors苇羡,然后依次添加 retryAndFollowUpInterceptor
(重試及重定向)锦茁、BridgeInterceptor
(請求參數(shù)的添加)码俩、CacheInterceptor
(緩存)稿存、ConnectInterceptor
(開始連接)瓣履、用戶自定義的 networkinterceptors
及 CallServerInterceptor
(發(fā)送參數(shù)并讀取響應)安聘。從這里可以知道浴韭,OkHttp 默認添加了好幾個 interceptor 用于完成不同的功能念颈。
在研究各個 interceptor 之前榴芳,需要考慮一下如何讓這些攔截器一個接著一個的執(zhí)行?繼續(xù)看上面的代碼歉井,在添加了各種 interceptors 之后哩至,創(chuàng)建了一個 RealInterceptorChain
對象菩貌。(它的構(gòu)造函數(shù)需要的參數(shù)很多虚茶,并且這些參數(shù)涉及到連接池、請求數(shù)據(jù)的發(fā)送等嘹叫。由于這篇文章主要分析 OkHttp 的基本流程,所以暫時略過這部分)RealInterceptorChain
是接口 Chain
的實現(xiàn)類鸣皂,Chain
是 鏈 的意思,其作用是把各個 Interceptor 串起來依次執(zhí)行仰泻。在獲得了 RealInterceptorChain
之后調(diào)用其 proceed
方法集侯,看名字就能知道是讓 Request
請求繼續(xù)執(zhí)行帜消。
下面具體分析 RealInterceptorChain
棠枉,它有如下的成員變量:
private final List<Interceptor> interceptors; // 攔截器
private final StreamAllocation streamAllocation; // 流管理器
private final HttpCodec httpCodec; // http流,發(fā)送請求數(shù)據(jù)并讀取響應數(shù)據(jù)
private final RealConnection connection; // scoket的連接
private final int index; // 當前攔截器的索引
private final Request request; // 當前的請求
private int calls; // chain 的 proceed 調(diào)用次數(shù)的記錄
其中 streamAllocation
泡挺、httpCodec
和 connection
都與 socket 連接有關(guān)辈讶,后續(xù)文章再分析÷γǎ看一下 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.
// 如果已經(jīng)有了一個流贱除,確保即將到來的 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().
// 如果已經(jīng)有了一個流,確保這是對 call 唯一的調(diào)用
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;
}
剛開始做了一些連接方面的判斷媳溺,需要關(guān)注的是標了(1)蝎困、(2)、(3)的幾行失暂,主要做了以下操作:
- 創(chuàng)建新的
RealInterceptorChain
决记,其中index
加1用于標識當前的攔截器 - 通過
index
獲取當前的攔截器 - 調(diào)用下一個攔截器的
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 {
// 調(diào)用 chain 的 proceed
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
... // 省略部分代碼韵丑,主要是錯誤重試以及重定向
}
}
這個 Interceptor 主要用于出錯重試以及重定向的邏輯陌僵,其中省略了部分代碼题涨。在這個方法當中要關(guān)注的是再次調(diào)用了 chain
的 proceed
方法,這里的 chain
是之前新創(chuàng)建的 next
對象。相當于說通過調(diào)用 Chain#proceed()
將網(wǎng)絡請求推向下一個攔截器(proceed
中會獲取下一個 Interceptor 并調(diào)用其 intercept
方法),并且得到 response 對象溶推,而下一個攔截器也是類似的操作辐赞。于是赘风,多個 interceptors 就通過這種方式串起來依次執(zhí)行鞍历,并且前一個 Interceptor 可以得到后一個 Interceptor 執(zhí)行后的 response 從而進行處理刑枝。
通過不同的 Interceptor,OkHttp 實現(xiàn)了不同的功能饱岸。各個 Inercept 職責分明又不會互相耦合百框,并且可以非常方便的添加 Interceptor,這是 責任鏈 模式的體現(xiàn)慎菲,非常優(yōu)雅的設計〖奚撸現(xiàn)在可以發(fā)現(xiàn) OkHttp 中的攔截器的調(diào)用過程如下圖所示:
![攔截器調(diào)用鏈](http://blog-ranze.oss-cn-hangzhou.aliyuncs.com/picture/%E6%8B%A6%E6%88%AA%E5%99%A8.png)
異步請求
相比于同步請求睬棚,異步請求主要是增加了 Dispatcher 的處理抑党。Dispatcher 是請求的分發(fā)器台汇,它有一下的成員變量:
private int maxRequests = 64; // 最大連接數(shù)
private int maxRequestsPerHost = 5; // 單個 host 最大連接數(shù)
private @Nullable Runnable idleCallback; // 空閑時的回調(diào)
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService; // 線程池
/** Ready async calls in the order they'll be run. */
// 準備執(zhí)行的異步 Call 的隊列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執(zhí)行的的異步 Call 的隊列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
// 正在執(zhí)行的同步 Call 的隊列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
在 Dispatcher 中粱挡,默認支持的最大并發(fā)連接數(shù)是64帚稠,每個 host 最多可以有5個并發(fā)請求翁锡。
下面看一下線程池 executorService
的創(chuàng)建蔓挖。線程池會在兩個地方創(chuàng)建夕土,分別是 Dispatcher 的構(gòu)造函數(shù)或者是 executorService
方法中(如果調(diào)用了默認的構(gòu)造函數(shù)):
// 默認構(gòu)造函數(shù)沒有創(chuàng)建
public Dispatcher() {
}
// 自定義線程池
public Dispatcher(ExecutorService executorService) {
this.executorService = executorService;
}
// 如果沒有自定義線程池,則默認創(chuàng)建
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 支持自定義的線程池瘟判,否則會默認創(chuàng)建一個怨绣。在生成 OkHttpClient
對象時,默認調(diào)用的是 Dispatcher 無參的構(gòu)造方法拷获。這個默認線程池通過 new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false))
創(chuàng)建篮撑,看上去類似于一個 CachedThreadPool,沒有常駐的 core 線程匆瓜,空閑線程60秒后自動關(guān)閉赢笨。
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);
}
}
具體步驟是:
- 判斷是否超出總共的最大連接數(shù)以及單個 host 的最大連接數(shù)
- 如果沒有則添加到
runningAsyncCalls
并且提交到線程池執(zhí)行 - 否則添加到
readyAsyncCalls
等待后續(xù)執(zhí)行
需要注意的是異步請求的 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 {
// 調(diào)用 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 中調(diào)用 execute
左冬,所以 AsyncCall
的主要邏輯都寫在 execute
中桐筏。可以看到最終還是調(diào)用了 getResponseWithInterceptorChain
方法拇砰,所以后續(xù)執(zhí)行網(wǎng)絡請求的邏輯是一樣的梅忌。在獲得 response 之后狰腌,就可以調(diào)用 responseCallback
返回最終的信息。
finished
在上面的代碼中牧氮,finally 里面執(zhí)行了 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,符合連接數(shù)要求時加入 runningAsyncCalls 并提交給線程池執(zhí)行剖毯。
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
有兩個重載的 finished
方法均調(diào)用了另一個 pirvate 的 finished
圾笨,區(qū)別在于這個 finished
的最后一個參數(shù) promoteCalls
。對于同步請求(參數(shù)為 RealCall
) promoteCalls
為 false
逊谋,而異步請求(參數(shù)為 AsyncCall
) promoteCalls
為 true
擂达。 pirvate 的 finished
主要是從隊列中移除 Call,異步請求會執(zhí)行 promoteCalls
胶滋。promoteCalls
里面主要是從 readyAsyncCalls
取出一個 Call板鬓,如果滿足最大連接數(shù)的要求,則把這個 Call 加入 runningAsyncCalls
并提交給線程池執(zhí)行究恤。
通過 runningAsyncCalls
和 readyAsyncCalls
俭令,Dispatcher 實現(xiàn)了異步請求的調(diào)度執(zhí)行。這里比較巧妙的方式是在 finally 中去執(zhí)行 readyAsyncCalls
中的請求部宿,避免了 wait/notity 的方式抄腔,避免了代碼的復雜性。
總結(jié)
OkHttp 的基本執(zhí)行流程如下圖所示:
![OKHttp 基本流程](http://blog-ranze.oss-cn-hangzhou.aliyuncs.com/picture/okhttp%E5%9F%BA%E6%9C%AC%E6%B5%81%E7%A8%8B.jpg)
主要是以下步驟:
-
OkHttpClient
調(diào)用newCall
創(chuàng)建RealCall
對象理张,Call
封裝了Request
赫蛇,代表一條即將執(zhí)行的請求。 - 根據(jù)同步還是異步請求分別調(diào)用
RealCall
的execute
或enqueue
方法雾叭,將Call
加入Dispatcher
的相應隊列中悟耘。最終,同步或異步請求都會調(diào)用getResponseWithInterceptorChain
织狐。 - 在
getResponseWithInterceptorChain
中暂幼,OkHttp 添加用戶自定義以及默認的 inceptors,并用一個Chain
管理并依次執(zhí)行每個 Interceptor移迫。 - 每個 Interceptor 調(diào)用
Chain#proceed()
將請求發(fā)送給下一級的 Inceptor旺嬉,并能通過這個方法獲得下一級 Interceptor 的 Response。所以上圖所示起意,Request 一級級地往下傳遞鹰服,而獲取了網(wǎng)絡的 Response 之后一級級地往上傳遞。
OkHttp中一條網(wǎng)絡請求的基本流程就是這樣,下一篇文章介紹 OkHttp 如何建立連接:OkHttp 源碼解析(二):建立連接悲酷。