系列索引
本系列文章基于 OkHttp3.14
OkHttp 源碼剖析系列(一)——請(qǐng)求的發(fā)起及攔截器機(jī)制概述
OkHttp 源碼剖析系列(六)——連接復(fù)用機(jī)制及連接的建立
OkHttp 源碼剖析系列(七)——請(qǐng)求的發(fā)起及響應(yīng)的讀取
前言
OkHttp 是一個(gè)我從學(xué) Android 開(kāi)始就接觸的網(wǎng)絡(luò)請(qǐng)求庫(kù)了雁刷,想想現(xiàn)在也陪伴它快兩年了,卻沒(méi)有系統(tǒng)性地對(duì)它進(jìn)行過(guò)一次系統(tǒng)性的源碼解析。因此準(zhǔn)備開(kāi)設(shè)這樣一個(gè)系列寂诱,對(duì) OkHttp 的源碼進(jìn)行解析强戴。
此篇源碼解析基于 OkHttp 3.14
OkHttpClient
我們都知道,使用 OkHttp 我們首先需要?jiǎng)?chuàng)建并獲得一個(gè) OkHttpClient
,OkHttpClient
是 OkHttp 中十分重要的一個(gè)類沼本,下面是官方在 Java Doc 中對(duì)它的介紹:
Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
responses.OkHttpClients should be shared
OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
all of your HTTP calls. This is because each client holds its own connection pool and thread
pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
client for each request wastes resources on idle pools.
根據(jù)官方對(duì)其的介紹可以看出兢交,它是一個(gè) Call
的工廠類薪捍,可以用它來(lái)生產(chǎn) Call
,從而通過(guò) Call
來(lái)發(fā)起 HTTP Request 獲取 Response。
同時(shí)酪穿,官方推薦的使用方式是使用一個(gè)全局的 OkHttpClient
在多個(gè)類之間共享凳干。因?yàn)槊總€(gè) Client 都會(huì)有一個(gè)自己的連接池和線程池,復(fù)用 Client 可以減少資源的浪費(fèi)被济。
它的構(gòu)建采用了 Builder 模式救赐,提供了許多可供我們配置的參數(shù):
public static final class Builder {
Dispatcher dispatcher;
@Nullable
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable
Cache cache;
@Nullable
InternalCache internalCache;
SocketFactory socketFactory;
@Nullable
SSLSocketFactory sslSocketFactory;
@Nullable
CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int callTimeout;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
// ...
}
可以看到,它的可配置的參數(shù)還是非常多的只磷。
構(gòu)建了 OkHttpClient
之后经磅,我們可以通過(guò) OkHttpClient.newCall
方法根據(jù)我們傳入的 Request
創(chuàng)建對(duì)應(yīng)的 Call
。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
Request
Request
所對(duì)應(yīng)的就是我們 HTTP 請(qǐng)求中的 Request钮追,可以對(duì)它的 url预厌、method、header 等在 Builder 中進(jìn)行配置元媚。
Request
的構(gòu)建同樣采用了 Builder 模式進(jìn)行構(gòu)建:
public static class Builder {
@Nullable
HttpUrl url;
String method;
Headers.Builder headers;
@Nullable
RequestBody body;
// ...
}
構(gòu)建完 Request
后轧叽,就可以調(diào)用 OkHttpClient.newCall
方法創(chuàng)建對(duì)應(yīng) Call
Call
構(gòu)建
我們知道,newCall
方法中調(diào)用了 RealCall.newRealCall(this, request, false /* for web socket */);
刊棕,其中第三個(gè)參數(shù)代表是否使用 web socket炭晒。
我們看看 RealCall.newRealCall
方法:
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.transmitter = new Transmitter(client, call);
return call;
}
這里根據(jù)我們傳入的參數(shù)構(gòu)建了一個(gè) RealCall
對(duì)象,并根據(jù) client
構(gòu)建了其 transmitter
鞠绰。
RealCall
的構(gòu)造函數(shù)中主要是一些賦值:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
}
而 Transmitter
中也是一些賦值操作:
public Transmitter(OkHttpClient client, Call call) {
this.client = client;
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
this.call = call;
this.eventListener = client.eventListenerFactory().create(call);
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
其中首先調(diào)用了 Internal.instance.realConnectionPool
方法腰埂,通過(guò) client.connectionPool
獲取到了 RealConnectionPool
對(duì)象,之后調(diào)用了 client.eventListenerFactory().create(call)
方法構(gòu)創(chuàng)建了其 eventListener
蜈膨。
請(qǐng)求的發(fā)起
OkHttp 的執(zhí)行有兩種方式屿笼,enqueue
及 execute
,它們分別代表了異步請(qǐng)求與同步請(qǐng)求:
enqueue
:代表了異步請(qǐng)求翁巍,不會(huì)阻塞調(diào)用線程驴一。需要我們傳入一個(gè)Callback
,當(dāng)請(qǐng)求成功時(shí)灶壶,會(huì)回調(diào)其onResponse
方法肝断,請(qǐng)求失敗時(shí)則會(huì)回調(diào)其onFailure
方法。execute
:代表了同步請(qǐng)求驰凛,會(huì)阻塞調(diào)用線程胸懈,請(qǐng)求結(jié)束后直接返回請(qǐng)求結(jié)果。
讓我們分別對(duì)其進(jìn)行分析:
異步請(qǐng)求
我們先分析一下 enqueue
方法:
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
// 通知eventListener
transmitter.callStart();
// 構(gòu)建AsyncCall并分派任務(wù)
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
它首先調(diào)用了 transmitter.callStart
恰响,最后調(diào)用到了之前構(gòu)造的 eventListener
的 callStart
方法
之后它調(diào)用了 client.dispatcher().enqueue
方法趣钱,構(gòu)建了一個(gè) AsyncCall
對(duì)象后交給了 client.dispatcher
進(jìn)行任務(wù)的分派。
executeOn
AsyncCall
類對(duì)外暴露了 executeOn
方法胚宦,Dispatcher
可以通過(guò)調(diào)用該方法并傳入 ExecutorService
使其在該線程池所提供的線程中發(fā)起 HTTP 請(qǐng)求首有,獲取 Response 并回調(diào) Callback
的對(duì)應(yīng)方法從而實(shí)現(xiàn)任務(wù)的調(diào)度燕垃。
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 在對(duì)應(yīng)的ExecutorService中執(zhí)行該AsyncCall
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
// 出現(xiàn)問(wèn)題,調(diào)用Callback對(duì)應(yīng)方法
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
// 不論是否成功井联,通知Dispatcher請(qǐng)求完成
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
可以看出卜壕,AsyncCall
是一個(gè) Runnable,我們看看它實(shí)現(xiàn)的 execute
方法:
@Override
protected void execute() {
boolean signalledCallback = false;
// 開(kāi)始Timeout計(jì)時(shí)
transmitter.timeoutEnter();
try {
// 獲取Response
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
// 請(qǐng)求成功烙常,通知Callback
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 {
// 請(qǐng)求失敗轴捎,通知Callback
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// 不論是否成功,通知Dispatcher請(qǐng)求完成
client.dispatcher().finished(this);
}
}
這里首先調(diào)用了 transmitter.timeoutEnter()
方法開(kāi)始了 Timeout 的計(jì)時(shí)军掂。
之后若請(qǐng)求成功轮蜕,則會(huì)通過(guò) getResponseWithInterceptorChain
方法獲取了 Response
昨悼,之后調(diào)用 Callback.onResponse
方法通知請(qǐng)求成功蝗锥。
若請(qǐng)求失敗,會(huì)調(diào)用 Callback.onFailure
方法通知請(qǐng)求失敗率触。
看來(lái)網(wǎng)絡(luò)請(qǐng)求的核心實(shí)現(xiàn)在 getResponseWithInterceptorChain
方法中實(shí)現(xiàn)终议,而 OkHttp 的超時(shí)機(jī)制與 transmitter.timeoutEnter
有關(guān),我們暫時(shí)先不關(guān)注這些細(xì)節(jié)葱蝗。
異步線程池
讓我們來(lái)看看 OkHttp 對(duì)異步請(qǐng)求采用了怎樣的線程池穴张。調(diào)用者在 AsyncCall.executeOn
方法中傳入了 Dispatcher.executorService
方法的返回值,我們來(lái)到此方法:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
我們知道两曼,這里的線程池是可以通過(guò)創(chuàng)建 Dispatcher
時(shí)指定的皂甘,若不指定,則這里會(huì)創(chuàng)建一個(gè)如上代碼中的線程池悼凑,我們來(lái)分析一下它的幾個(gè)參數(shù)偿枕。
- 核心線程數(shù)
corePoolSize
:保持在線程池中的線程數(shù),即使空閑后也不會(huì)保留户辫。由于為 0渐夸,因此任何線程空閑時(shí)都不會(huì)被保留。 - 最大線程數(shù)
maximumPoolSize
:線程池最大支持創(chuàng)建的線程數(shù)渔欢,這里指定了Integer.MAX_VALUE
墓塌。 - 線程存活時(shí)間
keepAliveTime
:線程空閑后所能存活的時(shí)間,若超過(guò)該時(shí)間就會(huì)被回收奥额。這里指定的時(shí)間為 60 個(gè)時(shí)間單位(60s)苫幢,也就是說(shuō)線程在空閑超過(guò) 60s 后就會(huì)被回收。 - 時(shí)間單位
unit
:簽名的線程存活時(shí)間的單位垫挨,這里為TimeUnit.SECONDS
韩肝,也就是說(shuō)秒 - 線程等待隊(duì)列
workQueue
:線程的等待隊(duì)列,里面的元素會(huì)按序排隊(duì)棒拂,依次執(zhí)行伞梯,這里和指定的是SynchronousQueue
玫氢。 - 線程工廠
threadFactory
:線程的創(chuàng)建工廠這里傳入的是Util.threadFactory
方法創(chuàng)建的線程工廠。
對(duì)于上面的幾個(gè)參數(shù)谜诫,我們有幾個(gè)細(xì)節(jié)需要考慮一下:
為什么要采用 SynchronousQueue
首先我們先需要了解一下什么是 SynchronousQueue
漾峡,它雖然是一個(gè)隊(duì)列,但它內(nèi)部不存在任何的容器喻旷,它采用了一種經(jīng)典的生產(chǎn)者-消費(fèi)者模型生逸,它有多個(gè)生產(chǎn)者和消費(fèi)者,當(dāng)一個(gè)生產(chǎn)線程進(jìn)行生產(chǎn)操作(put)時(shí)且预,若沒(méi)有消費(fèi)者線程進(jìn)行消費(fèi)(take)槽袄,那么該線程會(huì)阻塞,直到有消費(fèi)者進(jìn)行消費(fèi)锋谐。也就是說(shuō)遍尺,它僅僅實(shí)現(xiàn)了一個(gè)傳遞的操作,這種傳遞功能由于沒(méi)有了中間的放入容器涮拗,再?gòu)娜萜髦腥〕龅倪^(guò)程乾戏,因此是一種快速傳遞元素的方式就漾,這對(duì)于我們網(wǎng)絡(luò)請(qǐng)求這種高頻請(qǐng)求來(lái)說(shuō)懊蒸,是十分合適的妒貌。關(guān)于 SynchronousQueue
可以看這篇文章: java并發(fā)之SynchronousQueue實(shí)現(xiàn)原理
為什么線程池采用這種線程數(shù)量不設(shè)上限逆害,每個(gè)線程空閑時(shí)只存活很短時(shí)間的策略
實(shí)際上在 OkHttp 的設(shè)計(jì)中纯陨,將線程的個(gè)數(shù)的維護(hù)工作不再交給線程池活喊,而是由 Dispatcher
進(jìn)行實(shí)現(xiàn)软棺,通過(guò)外部所設(shè)置的 maxRequests
及 maxRequestsPerHost
來(lái)調(diào)整等待隊(duì)列及執(zhí)行隊(duì)列尤勋,從而實(shí)現(xiàn)對(duì)線程最大數(shù)量的控制喘落。具體 Dispatcher
的實(shí)現(xiàn)在本文后面會(huì)講到。
同步請(qǐng)求
execute
我們接著看到 execute
方法最冰,看看同步請(qǐng)求的執(zhí)行:
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
// 通知Dispatcher
client.dispatcher().executed(this);
// 獲取Response
return getResponseWithInterceptorChain();
} finally {
// 不論是否成功瘦棋,通知Dispatcher請(qǐng)求完成
client.dispatcher().finished(this);
}
}
它首先調(diào)用了 Dispatcher.executed
方法,通知 Dispatcher 該 Call 被執(zhí)行暖哨,之后調(diào)用到了 getResponseWithInterceptorChain
方法獲取 Response赌朋,不論是否成功都會(huì)調(diào)用 Dispatcher.finished
通知 Dispatcher 該 Call 執(zhí)行完成。
Dispatcher 任務(wù)調(diào)度
enqueue
我們看看 Dispatcher 是如何調(diào)度異步請(qǐng)求的篇裁,來(lái)到 Dispatcher.enqueue
方法:
void enqueue(AsyncCall call) {
synchronized (this) {
// 加入等待隊(duì)列
readyAsyncCalls.add(call);
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
// 尋找同一個(gè)host的Call
AsyncCall existingCall = findExistingCallWithHost(call.host());
// 復(fù)用Call的callsPerHost
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
// 嘗試執(zhí)行等待隊(duì)列中的任務(wù)
promoteAndExecute();
}
這里先將其加入了 readAsyncCalls
這一等待隊(duì)列中沛慢。
之后調(diào)用了 findExistingCallWithHost
方法嘗試尋找 host 相同的 Call
,它會(huì)遍歷 readyAsyncCalls
及 runningAsyncCalls
兩個(gè)隊(duì)列尋找 host 相同的 Call达布。
若找到了對(duì)應(yīng)的 Call
团甲,則會(huì)調(diào)用 call.reuseCallsPerHostFrom
來(lái)復(fù)用這個(gè) Call 的 callsPerHost
,從而便于統(tǒng)計(jì)一個(gè) host 對(duì)應(yīng)的 Call
的個(gè)數(shù)黍聂,它是一個(gè) AtomicInteger
躺苦。
最后會(huì)調(diào)用 promoteAndExecute
方法身腻,這個(gè)方法會(huì)嘗試將等待隊(duì)列中的任務(wù)執(zhí)行。
executed
我們繼續(xù)看看 Dispatcher 是如何調(diào)度同步請(qǐng)求的匹厘,來(lái)到 Dispatcher.executed
方法:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
這里很簡(jiǎn)單霸株,直接將該同步任務(wù)加入了執(zhí)行隊(duì)列 runningSyncCalls
中。
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 (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
// 增加host對(duì)應(yīng)執(zhí)行中call的數(shù)量
asyncCall.callsPerHost().incrementAndGet();
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;
}
在這個(gè)方法中遍歷了 readyAsyncCalls
隊(duì)列集乔,不斷地尋找能夠執(zhí)行的 AsynCall
去件,若找到則會(huì)在最后統(tǒng)一調(diào)用 AsyncCall.executeOn
方法在自己的 executorService
線程池中執(zhí)行該 Call。其中扰路,執(zhí)行中的任務(wù)不能超過(guò) maxRequests
尤溜。
finished
我們從前面 AsyncCall
的實(shí)現(xiàn)可以看出,每次請(qǐng)求完成后汗唱,不論成功失敗宫莱,都會(huì)調(diào)用到 finished
方法通知 Dispatcher 請(qǐng)求結(jié)束:
void finished(AsyncCall call) {
// 減少host對(duì)應(yīng)執(zhí)行中call的數(shù)量
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
它調(diào)用到了 finished
的另一個(gè)重載:
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
// 嘗試執(zhí)行等待隊(duì)列中的任務(wù)
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
可以看到,這里再次調(diào)用了 promoteAndExecute
方法嘗試執(zhí)行等待隊(duì)列中的任務(wù)哩罪,若當(dāng)前等待隊(duì)列中沒(méi)有需要執(zhí)行的任務(wù)授霸,說(shuō)明目前還比較空閑,沒(méi)有到達(dá)設(shè)定的 maxRequests
际插。此時(shí)會(huì)調(diào)用 idleCallback.run
執(zhí)行一些空閑 Callback
(這種設(shè)計(jì)有點(diǎn)類似 Handler
的 IdleHandler
機(jī)制碘耳,充分利用了一些空閑資源,值得我們學(xué)習(xí))框弛。
小結(jié)
可以看出辛辨,OkHttp 的任務(wù)的調(diào)度器的設(shè)計(jì)將請(qǐng)求分別分至了兩個(gè)隊(duì)列中,分別是等待隊(duì)列及執(zhí)行隊(duì)列瑟枫。
每次加入新的異步請(qǐng)求時(shí)斗搞,都會(huì)先將其加入等待隊(duì)列,之后遍歷等待隊(duì)列嘗試執(zhí)行等待任務(wù)慷妙。
每次加入新的同步請(qǐng)求時(shí)僻焚,都會(huì)直接將其加入執(zhí)行隊(duì)列。
而每當(dāng)一個(gè)請(qǐng)求完成時(shí)膝擂,都會(huì)通知到 Dispatcher虑啤,Dispatcher 會(huì)遍歷準(zhǔn)備隊(duì)列嘗試執(zhí)行任務(wù),若沒(méi)有執(zhí)行則說(shuō)明等待隊(duì)列是空的猿挚,則會(huì)調(diào)用 idleCallback.run
執(zhí)行一些空閑時(shí)的任務(wù)咐旧,類似 Handler 的 IdleHandler 機(jī)制。
(在多線程下載器中的任務(wù)調(diào)度器就用到了這里的 Dispatcher 的設(shè)計(jì))
響應(yīng)的獲取
從前面的同步和異步請(qǐng)求中都可以看出绩蜻,響應(yīng)的獲取的核心實(shí)現(xiàn)是 RealCall.getResponseWithInterceptorChain
方法:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
// 初始化攔截器列表
List<Interceptor> interceptors = new ArrayList<>();
// 用戶自定義的 Interceptor
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 用戶自定義的網(wǎng)絡(luò) Interceptor
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
這個(gè)方法非常重要铣墨,短短幾行代碼就實(shí)現(xiàn)了對(duì)請(qǐng)求的所有處理,它體現(xiàn)了 OkHttp 中一個(gè)很重要的核心設(shè)計(jì)——攔截器機(jī)制办绝。
它首先在 interceptors
中加入了用戶自定義的攔截器伊约,之后又按順序分別加入了各種系統(tǒng)內(nèi)置的攔截器姚淆。
之后通過(guò) RealInterceptorChain
的構(gòu)造 函數(shù)構(gòu)造了一個(gè) Chain
對(duì)象,之后調(diào)用了其 proceed
方法屡律,從而得到了該請(qǐng)求的 Response腌逢。
那么這個(gè)過(guò)程中究竟是如何獲取到 Response 的呢?讓我們先理解一下 OkHttp 的攔截器機(jī)制超埋。
攔截器機(jī)制概述
OkHttp 的網(wǎng)絡(luò)請(qǐng)求的過(guò)程就是依賴于各種攔截器(Interceptor)實(shí)現(xiàn)的搏讶,我們先看看 Interceptor
的定義:
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable
Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
Interceptor
實(shí)際上是一個(gè)接口,里面只有一個(gè)方法 intercept
以及一個(gè)接口 Chain
霍殴。
Interceptor
其中媒惕,intercept
方法往往是如下的結(jié)構(gòu):
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Request階段,該攔截器在Request階段負(fù)責(zé)做的事情
// 調(diào)用RealInterceptorChain.proceed()来庭,其實(shí)是在遞歸調(diào)用下一個(gè)攔截器的intercept()方法
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
// Response階段妒蔚,完成了該攔截器在Response階段負(fù)責(zé)做的事情,然后返回到上一層的攔截器月弛。
return response;
}
這里先調(diào)用了 chain.request
方法獲取到了本次請(qǐng)求的 Request
對(duì)象肴盏,
之后調(diào)用了 chain.proceed
方法遞歸調(diào)用下一個(gè)攔截器的 interceptor
方法。
最后返回了 chain.proceed
方法所返回的 Response
帽衙。
上面簡(jiǎn)單的三行代碼將整個(gè) intercept
過(guò)程分為了兩個(gè)階段:
- Request 階段:執(zhí)行一些該攔截器在 Request 階段所負(fù)責(zé)的事情
- Response 階段:完成該攔截器在 Response 階段所負(fù)責(zé)的事情
這其實(shí)是采用了一種遞歸的設(shè)計(jì)菜皂,類似我們計(jì)算機(jī)網(wǎng)絡(luò)中的分層模型,將 OkHttp 的請(qǐng)求分為了幾個(gè)階段佛寿,分別代表了不同的攔截器幌墓,不同攔截器分別會(huì)在這個(gè)遞歸的過(guò)程中有兩次對(duì)該請(qǐng)求的處理的可能,一次是在 Request 之前冀泻,一次是在 Response 之后,中間的過(guò)程中若出現(xiàn)了錯(cuò)誤蜡饵,則通過(guò)拋出異常來(lái)通知上層弹渔。
預(yù)置的 Interceptor
有如下幾種:
- RetryAndFollowUpInterceptor:負(fù)責(zé)實(shí)現(xiàn)重定向功能
- BridgeInterceptor:將用戶構(gòu)造的請(qǐng)求轉(zhuǎn)換為向服務(wù)器發(fā)送的請(qǐng)求,將服務(wù)器返回的響應(yīng)轉(zhuǎn)換為對(duì)用戶友好的響應(yīng)
- CacheInterceptor:讀取緩存溯祸、更新緩存
- ConnectInterceptor:建立與服務(wù)器的連接
- CallServerInterceptor:從服務(wù)器讀取響應(yīng)
可以看出肢专,整個(gè)網(wǎng)絡(luò)請(qǐng)求的過(guò)程由各個(gè)攔截器互相配合從而實(shí)現(xiàn),通過(guò)這種攔截器的機(jī)制焦辅,可以很方便地調(diào)節(jié)網(wǎng)絡(luò)請(qǐng)求的過(guò)程及先后順序博杖,同時(shí)也能夠很方便地使用戶對(duì)其進(jìn)行擴(kuò)展。
其中用戶可以在兩個(gè)時(shí)機(jī)插入 Interceptor:
- 網(wǎng)絡(luò)請(qǐng)求前后:通過(guò)
OkHttpClient.addInterceptor
方法添加 - 讀取響應(yīng)前后:通過(guò)
OkHttpClient.addNetworkInterceptor
方法添加
其整體流程如圖所示:
RealInterceptorChain
我們?cè)倏纯词侨绾瓮ㄟ^(guò) RealInterceptorChain
將整個(gè)攔截器的調(diào)用過(guò)程連接起來(lái)的筷登,我們先看看其構(gòu)造過(guò)程:
public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
@Nullable Exchange exchange, int index, Request request, Call call,
int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.transmitter = transmitter;
this.exchange = exchange;
this.index = index;
this.request = request;
this.call = call;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
這里只是一些賦值過(guò)程剃根,我們接著看到 chain.proceed
方法,看看它是如何執(zhí)行的:
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
// ...
// 構(gòu)建下一個(gè)Interceptor的Chain
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
// 獲取當(dāng)前Interceptor并執(zhí)行intercept方法
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// ...
return response;
}
這里省略了一些異常處理前方,可以看到它首先構(gòu)造了下一個(gè)攔截器對(duì)應(yīng)的 Chain狈醉,之后獲取到了當(dāng)前的攔截器并調(diào)用了其 intercept
方法獲取其結(jié)果廉油,在 intercept
方法的參數(shù)中傳入的就是下一個(gè)攔截器對(duì)應(yīng)的 Chain
。
通過(guò)這種遞歸的設(shè)計(jì)苗傅,從而實(shí)現(xiàn)了從上到下抒线,再?gòu)南碌缴线@樣一個(gè)遞與歸的過(guò)程,從而十分漂亮地實(shí)現(xiàn)了 HTTP 請(qǐng)求的全過(guò)程渣慕。
這是一種類似責(zé)任鏈模式的實(shí)現(xiàn)嘶炭,這樣的實(shí)現(xiàn)在網(wǎng)絡(luò)請(qǐng)求的過(guò)程中十分常見(jiàn),也十分值得我們?nèi)W(xué)習(xí)逊桦。
小結(jié)
OkHttp 在讀取響應(yīng)的過(guò)程中采用了一種責(zé)任鏈模式旱物,預(yù)置了多個(gè)負(fù)責(zé)不同功能的攔截器,將它們通過(guò)責(zé)任鏈連接在一起卫袒,采用了一種遞歸的方式進(jìn)行調(diào)用宵呛,從而使得每一層在請(qǐng)求前和響應(yīng)后都能對(duì)本次請(qǐng)求作出不同的處理,通過(guò)各個(gè)攔截器的協(xié)調(diào)合作夕凝,最終完成了整個(gè)網(wǎng)絡(luò)請(qǐng)求的過(guò)程宝穗。