本文意圖是針對面試敬锐,理清內部流程關系传轰,而非細摳代碼剩盒,匯總他人的文字而來,原地址 youyuge.cn
1.概述
okhttp3總體流程圖:
先來回顧一下代碼的使用流程,然后跟著流程一步步來分析:
1.1 創(chuàng)建 OkHttpClient
對象
OkHttpClient client = new OkHttpClient();
其實okHttpClient
用的也是builder
構建者模式:
public OkHttpClient() {
this(new Builder());
}
實際上慨蛙,OkHttpClient.Builder
類成員很多辽聊,用來設置連接的各種參數(shù),官方建議使用單例模式構建一個client期贫。 直接new只是使用了默認配置:
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS;
connectionSpecs = DEFAULT_CONNECTION_SPECS;
proxySelector = ProxySelector.getDefault();
cookieJar = CookieJar.NO_COOKIES;
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE;
certificatePinner = CertificatePinner.DEFAULT;
proxyAuthenticator = Authenticator.NONE;
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool();
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
1.2 創(chuàng)建Request
請求對象
Request request = new Request.Builder()
.url(url)
.build();
1.3 創(chuàng)建Call
對象
OkHttpClient 實現(xiàn)了Call.Factory
跟匆,負責根據(jù)請求創(chuàng)建新的 Call。
callFactory 負責創(chuàng)建 HTTP 請求通砍,HTTP 請求被抽象為了 okhttp3.Call 類玛臂,它表示一個已經準備好,可以隨時執(zhí)行的 HTTP 請求
Call call = client.newCall(request);
1.4 同步阻塞執(zhí)行請求
Response response = call.execute();
1.5 異步回調執(zhí)行請求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
System.out.println(response.body().string());
}
});
2. 精妙的線程池
2.1 同步請求時
實際上newCall
方法會返回一個Realcall
對象封孙,而它同步執(zhí)行execute()方法時:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
//僅僅標記作用
client.dispatcher().executed(this);
//這才是真正的執(zhí)行下一步發(fā)請求迹冤,同步異步都會走到這個方法
Response result = getResponseWithInterceptorChain(false);
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
雖然dispatcher也摻和了,其實沒什么用虎忌,涉及到 dispatcher 的內容只不過是在內部做了個標記泡徙,表明有個有個任務正在執(zhí)行:
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
2.2 異步請求
2.2.1 反向代理模型
服務器會添加Header并自動轉發(fā)請求給后端集群,接著返回數(shù)據(jù)結果給用戶呐籽》嫔祝可以提高服務的負載均衡能力,實現(xiàn)非阻塞狡蝶、高可用庶橱、高并發(fā)連接,避免資源全部放到一臺服務器而帶來的負載贪惹。
而在OkHttp中苏章,非常類似于上述場景,它使用Dispatcher
作為任務的派發(fā)器奏瞬,線程池對應多臺后置服務器枫绅,用AsyncCall
對應Socket請求,用Deque<readyAsyncCalls>
對應Nginx的內部緩存硼端。
具體成員如下
- maxRequests = 64: 最大并發(fā)請求數(shù)為64
- maxRequestsPerHost = 5: 每個主機最大請求數(shù)為5
- Dispatcher: 分發(fā)者并淋,也就是生產者(默認在主線程)
- AsyncCall: 隊列中需要處理的Runnable(包裝了異步回調接口)
- ExecutorService:消費者池(也就是線程池)
-
Deque<readyAsyncCalls>
:緩存(用數(shù)組實現(xiàn),可自動擴容珍昨,無大小限制) -
Deque<runningAsyncCalls>
:正在運行的任務县耽,僅僅是用來引用正在運行的任務以判斷并發(fā)量句喷,注意它并不是消費者緩存
通過將請求任務分發(fā)給多個線程,可以顯著的減少I/O等待時間兔毙。
2.2.2 異步執(zhí)行過程
具體過程如下:
-
enqueue()
方法調用后唾琼,判斷runningAsyncCalls
隊列是否還有空位子。 - 若有空位澎剥,那就調用
executorService().execute(call);
交給線程池執(zhí)行請求锡溯,并且添加進運行隊列 - 啊,沒空位了哑姚,只能進
readyAsyncCalls
等待隊列了……
看一下call任務內部的執(zhí)行方法:
…………
//果然和同步方式一樣祭饭,最后通過這個方法去發(fā)送給下一步。
Response response = getResponseWithInterceptorChain(forWebSocket);
…………
finally {
//最關鍵的代碼
client.dispatcher().finished(this);
}
當任務執(zhí)行完成后蜻懦,無論是否有異常甜癞,finally代碼段總會被執(zhí)行,也就是會調用Dispatcher
的finished函數(shù)宛乃,打開源碼悠咱,發(fā)現(xiàn)它將正在運行的任務Call從隊列runningAsyncCalls
中移除后,接著執(zhí)行promoteCalls()
函數(shù)
if (runningCallsForHost(call) < maxRequestsPerHost) {
//將緩存等待區(qū)最后一個移動到運行區(qū)中征炼,并執(zhí)行
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
實際上就是會在任務執(zhí)行完的最后析既,把自己從運行隊列移除,喚醒一個等待隊列的任務谆奥,讓他去執(zhí)行眼坏。
這樣,就主動的把緩存隊列向前走了一步酸些,而沒有使用互斥鎖等復雜編碼宰译。
2.2.3 異步總結
- OkHttp采用Dispatcher技術,反向代理魄懂,優(yōu)化了單生產者多消費者模式沿侈,與線程池配合實現(xiàn)了高并發(fā),低阻塞的運行
- Okhttp采用Deque作為緩存市栗,按照入隊的順序先進先出
- OkHttp最出彩的地方就是在try/finally中調用了finished函數(shù)缀拭,可以在任務結束時候喚醒等待的任務,主動控制等待隊列的移動填帽,而不是采用鎖或者wait/notify蛛淋,極大減少了編碼復雜性
3. 工作的核心----攔截器
3.1 攔截器的介紹與種類
不管同步異步發(fā)送任務請求,最后都會執(zhí)行getResponseWithInterceptorChain
:
private 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 (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
攔截器是個啥篡腌?官方介紹和圖片:
the whole thing is just a stack of built-in interceptors.
可見 Interceptor 是 OkHttp 最核心的一個東西褐荷,不要誤以為它只負責攔截請求進行一些額外的處理(例如 cookie),實際上它把實際的網絡請求嘹悼、緩存诚卸、透明壓縮等功能都統(tǒng)一了起來葵第,每一個功能都只是一個 Interceptor绘迁,它們再連接成一個 Interceptor.Chain
合溺,環(huán)環(huán)相扣,最終圓滿完成一次網絡請求缀台。
從 getResponseWithInterceptorChain
函數(shù)我們可以看到棠赛,Interceptor.Chain
的分布依次是:
- 在配置
OkHttpClient
時設置的interceptors
; - 負責失敗重試以及重定向的
RetryAndFollowUpInterceptor
膛腐; - 負責把用戶構造的請求轉換為發(fā)送到服務器的請求睛约、把服務器返回的響應轉換為用戶友好的響應的
BridgeInterceptor
; - 負責讀取緩存直接返回哲身、更新緩存的
CacheInterceptor
辩涝; - 負責和服務器建立連接的
ConnectInterceptor
; - 配置 OkHttpClient 時設置的
networkInterceptors
勘天; - 負責向服務器發(fā)送請求數(shù)據(jù)怔揩、從服務器讀取響應數(shù)據(jù)的
CallServerInterceptor
。
3.2 攔截器的責任鏈模式
實際上脯丝,是責任鏈模式的最佳應用(如同事件分發(fā)機制)商膊,每個攔截器可以自己攔截處理,或者交給下一個攔截器介杆,讓每個 Interceptor
自行決定能否完成任務以及怎么完成任務捞慌。
其實 Interceptor
的設計也是一種分層的思想胸哥,每個 Interceptor
就是一層。為什么要套這么多層呢实幕?分層的思想在 TCP/IP
協(xié)議中就體現(xiàn)得淋漓盡致,分層簡化了每一層的邏輯堤器,每層只需要關注自己的責任(單一原則思想也在此體現(xiàn))昆庇,而各層之間通過約定的接口/協(xié)議進行合作(面向接口編程思想),共同完成復雜的任務吼旧。
4 總結
-
OkHttpClient
實現(xiàn)Call.Factory
凰锡,負責為 Request 創(chuàng)建 Call; -
RealCall
為具體的 Call 實現(xiàn)圈暗,其enqueue()
異步接口通過Dispatcher
利用ExecutorService
實現(xiàn)掂为,而最終進行網絡請求時和同步execute()
接口一致,都是通過getResponseWithInterceptorChain()
函數(shù)實現(xiàn)员串; -
getResponseWithInterceptorChain()
中利用Interceptor
鏈條勇哗,分層實現(xiàn)緩存、透明壓縮寸齐、網絡 IO 等功能欲诺;
引用