點(diǎn)贊關(guān)注鹉动,不再迷路浑厚,你的支持對(duì)我意義重大摹闽!
?? Hi蹄咖,我是丑丑。本文「Android 路線」| 導(dǎo)讀 —— 從零到無(wú)窮大 已收錄付鹿。這里有 Android 進(jìn)階成長(zhǎng)路線筆記 & 博客澜汤,歡迎跟著彭丑丑一起成長(zhǎng)蚜迅。(聯(lián)系方式在 GitHub)
前言
- 網(wǎng)絡(luò)請(qǐng)求是 App 中非常重要的一個(gè)組件,而 OkHttp 作為官方和業(yè)界雙重認(rèn)可的解決方案俊抵,其學(xué)習(xí)價(jià)值不必多言谁不;
- 在這篇文章里,我將分析 OkHttp 分發(fā)器 & 攔截器 的實(shí)現(xiàn)原理徽诲。如果能幫上忙刹帕,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要谎替。
目錄
1. 前置知識(shí)
Editting...
2. OkHttp 簡(jiǎn)介
OkHttp 是 Square 開(kāi)源的網(wǎng)絡(luò)請(qǐng)求框架偷溺,自從 Android 4.4 移除 HttpURLConnection 后,逐漸演變成 Android 端最主流的網(wǎng)絡(luò)請(qǐng)求框架钱贯。
2.1 優(yōu)點(diǎn)
- 1挫掏、支持 Http1、Http2秩命、Quic 以及 WebSocket 等多種應(yīng)用層協(xié)議尉共;
- 2、連接池復(fù)用底層 TCP 連接(Socket)弃锐,降低了請(qǐng)求時(shí)延袄友;
- 3、無(wú)縫支持 GZIP 減少數(shù)據(jù)流量拿愧;
- 4杠河、緩存響應(yīng)數(shù)據(jù),減少了重復(fù)網(wǎng)絡(luò)請(qǐng)求浇辜;
- 5、自動(dòng)失敗重試唾戚、自動(dòng)重定向柳洋。
2.2 使用流程
一次 OkHttp 請(qǐng)求過(guò)程最少需要用到 OkHttpClient、Request叹坦、Call熊镣、Response,例如:
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
同步請(qǐng)求
Call call = client.newCall(request);
獲得響應(yīng)
Response response = call.execute();
異步請(qǐng)求
Call call = client.newCall(request);
call.enqueue(new Callback(){
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) {
獲得響應(yīng)
}
});
需要注意的是募书,Call 是一個(gè)接口绪囱,OkHttpClient#newCall(...)
返回的其實(shí)是它的實(shí)現(xiàn)類(lèi) RealCall:
OkHttpClient.java
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false);
}
RealCall.java
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
3. Dispatcher 分發(fā)器
Call#execute() & Call#enqueue(...)
分別表示調(diào)用同步請(qǐng)求和異步請(qǐng)求,客戶端對(duì)請(qǐng)求任務(wù)的調(diào)度過(guò)程是不感知的莹捡。這是因?yàn)?OkHttp 內(nèi)部的 「Dispatcher 分發(fā)器」 封裝了整個(gè)請(qǐng)求任務(wù)的調(diào)度過(guò)程鬼吵,這一節(jié)我們來(lái)具體分析下。
3.1 自定義 Dispatcher 分發(fā)器
Dispatcher 主要成員變量如下:
Dispatcher.java
異步請(qǐng)求最大并發(fā)數(shù)
private int maxRequests = 64;
同一域名請(qǐng)求最大并發(fā)數(shù)
private int maxRequestsPerHost = 5;
閑置任務(wù)(沒(méi)有請(qǐng)求時(shí)執(zhí)行)
private Runnable idleCallback;
線程池
private ExecutorService executorService;
異步請(qǐng)求等待隊(duì)列
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque();
異步請(qǐng)求執(zhí)行隊(duì)列
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque();
同步請(qǐng)求執(zhí)行隊(duì)列
private final Deque<RealCall> runningSyncCalls = new ArrayDeque();
其中篮赢,以下幾個(gè)變量是允許自定義的:
變量 | 描述 | 默認(rèn)值 |
---|---|---|
maxRequests | 異步請(qǐng)求最大并發(fā)數(shù) | 64 |
maxRequestsPerHost | 同一域名請(qǐng)求最大并發(fā)數(shù) | 5 |
idleCallback | 閑置任務(wù) | null |
executorService | 線程池 | 無(wú)等待齿椅,最大并發(fā) |
提示: 限制請(qǐng)求數(shù)是為了避免客戶端和服務(wù)器的負(fù)載過(guò)大(Linux 一切皆文件琉挖,Socket 占用文件文件句柄,打開(kāi)文件數(shù)是有限制的)涣脚,至于為什么默認(rèn)值為 64 和 5示辈,據(jù)說(shuō) OkHttp 是參考了主流瀏覽器得出的經(jīng)驗(yàn)值,未查及論據(jù)遣蚀。
自定義的 Dispatcher 對(duì)象通過(guò) OkHttpClient.Builder 構(gòu)建者設(shè)置:
public OkHttpClient.Builder dispatcher(Dispatcher dispatcher) {
if (dispatcher == null) {
throw new IllegalArgumentException("dispatcher == null");
} else {
this.dispatcher = dispatcher;
return this;
}
}
3.2 同步請(qǐng)求
這一節(jié)我們來(lái)分析 OkHttp 同步請(qǐng)求的執(zhí)行過(guò)程:
已簡(jiǎn)化
public Response execute() throws IOException {
1矾麻、禁止重復(fù)執(zhí)行
synchronized(this) {
if (this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
2、記錄
this.client.dispatcher().executed(this);
3芭梯、執(zhí)行請(qǐng)求射富,阻塞等待響應(yīng)返回
Response result = this.getResponseWithInterceptorChain();
4、移除記錄
this.client.dispatcher().finished(this);
return result;
}
記錄到 runningSyncCalls 中
synchronized void executed(RealCall call) {
this.runningSyncCalls.add(call);
}
從 runningSyncCalls 中移除
void finished(RealCall call) {
內(nèi)部邏輯見(jiàn) 第 3.3 節(jié)
finished(runningSyncCalls, call);
}
同步請(qǐng)求是在當(dāng)前線程阻塞執(zhí)行粥帚,所以不需要 Dispatcher 調(diào)度任務(wù)胰耗,Dispatcher 內(nèi)部也僅僅是通過(guò) runningSyncCalls 記錄同步請(qǐng)求。
同時(shí)可以看到芒涡,RealCall#getResponseWithInterceptorChain()
是真正執(zhí)行請(qǐng)求的地方柴灯,我一并在 「Android 路線」| OkHttp 攔截器 中分析。
3.3 異步請(qǐng)求
這一節(jié)我們來(lái)分析 OkHttp 異步請(qǐng)求的執(zhí)行過(guò)程:
RealCall.java
已簡(jiǎn)化
public void enqueue(Callback responseCallback) {
1费尽、禁止重復(fù)執(zhí)行
synchronized(this) {
if (this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
2赠群、調(diào)用分發(fā)器
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback));
}
OkHttp 異步請(qǐng)求就需要 Dispatcher 登場(chǎng)了,我們來(lái)看看:
Dispatcher.java
void enqueue(AsyncCall call) {
synchronized (this) {
2.1 加入 readyAsyncCalls
readyAsyncCalls.add(call);
if (!call.get().forWebSocket) {
2.2 復(fù)用同一個(gè)計(jì)數(shù)器(記錄同一域名異步請(qǐng)求數(shù))
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
2.3 觸發(fā)或執(zhí)行請(qǐng)求
promoteAndExecute();
}
主要的邏輯應(yīng)該在 Dispatcher#promoteAndExecute()
中:
2.3 觸發(fā)或執(zhí)行請(qǐng)求
private boolean promoteAndExecute() {
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
1旱幼、遍歷 readyAsyncCalls 列表
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
2查描、判斷全部異步請(qǐng)求并發(fā)數(shù)是否超過(guò)限制
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
3、判斷同一域名異步請(qǐng)求并發(fā)數(shù)是否超過(guò)限制
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
4柏卤、符合執(zhí)行條件
i.remove();
4.1冬三、同一域名異步請(qǐng)求數(shù)加一
asyncCall.callsPerHost().incrementAndGet();
4.2 可執(zhí)行任務(wù)列表
executableCalls.add(asyncCall);
4.3 執(zhí)行中任務(wù)列表
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
5 處理可執(zhí)行任務(wù)列表
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
執(zhí)行請(qǐng)求
asyncCall.executeOn(executorService());
}
6 返回是否正在執(zhí)行請(qǐng)求(若無(wú)請(qǐng)求,則執(zhí)行閑置任務(wù) idleCallback)
return isRunning;
}
可以看到缘缚,異步請(qǐng)求是存在限制的勾笆,只有 「全部異步請(qǐng)求并發(fā)數(shù)不超過(guò)限制」 & 「同一域名異步請(qǐng)求并發(fā)數(shù)不超過(guò)限制」,才允許執(zhí)行桥滨,否則會(huì)停留在 readyAsyncCalls 中等待窝爪。
那么,誰(shuí)來(lái)拯救在 readyAsyncCalls 中等待的請(qǐng)求呢齐媒?其實(shí)就是看什么時(shí)候會(huì)觸發(fā)promoteAndExecute()
方法了蒲每,除了 setMaxRequests(int)、setMaxRequestsPerHost(int)
外喻括,在異步請(qǐng)求完成后邀杏,讓出 “空閑名額” 也會(huì)觸發(fā)。
AsyncCall.java
5 處理可執(zhí)行任務(wù)列表
void executeOn(ExecutorService executorService) {
boolean success = false;
try {
5.1 線程池執(zhí)行
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
5.2 線程池拒絕執(zhí)行
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
eventListener.callFailed(RealCall.this, ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
5.3 finish
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
5.1 線程池執(zhí)行(已簡(jiǎn)化)
protected void execute() {
try {
5.1.1 執(zhí)行請(qǐng)求双妨,阻塞等待響應(yīng)返回
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
5.1.2 請(qǐng)求取消
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
5.1.3 請(qǐng)求成功
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
5.1.4 請(qǐng)求失敗
responseCallback.onFailure(RealCall.this, e);
} finally {
5.1.5 finish
client.dispatcher().finished(this);
}
}
這段代碼不算復(fù)雜淮阐,AsyncCall 是 Runnable 的子類(lèi)叮阅,Runnable#run()
最終會(huì)走到AsyncCall#execute()
,主要分為三步:
- 調(diào)用
getResponseWithInterceptorChain()
來(lái)獲得請(qǐng)求響應(yīng)泣特,見(jiàn) 「Android 路線」| OkHttp 攔截器浩姥; - 調(diào)用 responseCallback 回調(diào);
- 調(diào)用 Dispatcher#finished()状您。
Dispatcher#finished() 我們?cè)?第 3.2 節(jié) 同步請(qǐng)求里見(jiàn)過(guò)勒叠,現(xiàn)在我們來(lái)分析下:
Dispatcher.java
5.1.5 finish 或 5.3 finish
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
1、從移除請(qǐng)求執(zhí)行列表中移除
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
2膏孟、觸發(fā)或執(zhí)行請(qǐng)求
boolean isRunning = promoteAndExecute();
3眯分、若無(wú)請(qǐng)求,則執(zhí)行閑置任務(wù) idleCallback
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
4. 分發(fā)器線程池
為什么不能使用ArrayBlockingQueue()
為什么不能使用LinkedBlockingQueue
為什么使用SynchronousQueue
5. 總結(jié)
看到這里柒桑,我們先來(lái)總結(jié)這篇文章的內(nèi)容以及遇到的疑問(wèn):
- 1弊决、同步請(qǐng)求不需要 Dispatcher 任務(wù)調(diào)度,Dispatcher 只做記錄魁淳;
- 2 飘诗、異步請(qǐng)求有限制,被限制的請(qǐng)求會(huì)在 ready 隊(duì)列中等待界逛,直到有請(qǐng)求完成讓出 “空閑名額” 才會(huì)觸發(fā)執(zhí)行昆稿;
在前面關(guān)于同步請(qǐng)求和異步請(qǐng)求的分析中,我們都提到了 RealCall#getResponseWithInterceptorChain()
是真正執(zhí)行請(qǐng)求的地方息拜,我在 「Android 路線」| OkHttp 攔截器 里討論溉潭。
創(chuàng)作不易,你的「三連」是丑丑最大的動(dòng)力少欺,我們下次見(jiàn)喳瓣!