OkHttp解析系列
OkHttp解析(一)從用法看清原理
OkHttp解析(二)網(wǎng)絡(luò)連接
OkHttp解析(三)關(guān)于Okio
認(rèn)識(shí)使用
一系列是對(duì)OkHttp的源碼解析,就從大家使用方法入手
常用方法
OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS) .cookieJar(new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies)
{
//...
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
//...
return null;
}
}).build();
Request request = new Request.Builder().url("www.baidu.com").build();
Call call = okHttpClient.newCall(request);
//異步
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
可以看到常用的用法禽拔,是創(chuàng)建OKHttpClient對(duì)象优烧,構(gòu)建請(qǐng)求Request躏惋,請(qǐng)求調(diào)用Call耻陕,而Call里面對(duì)應(yīng)有同步異步兩種方法,同步下則要在線程中去進(jìn)行恰矩。這就是大家常用的方法,我們先來(lái)看看OkHttpClient
OkHttpClient
我們先看注釋說(shuō)明
/**
* Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their responses.
OkHttpClient算是執(zhí)行調(diào)用請(qǐng)求Call的工廠谦去,這個(gè)工廠將會(huì)被用來(lái)發(fā)送Http請(qǐng)求和讀取他們的返回
*
* ...
*
* <p>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.
這里強(qiáng)調(diào)OkHttp的使用最好創(chuàng)建一個(gè)單例OkHttpClient實(shí)例慷丽,并且重復(fù)使用。這是因?yàn)槊恳粋€(gè)Client都有自己的一個(gè)連接池connection pool和線程池thread pools鳄哭。重用這些連接池和線程池可以減少延遲和節(jié)約內(nèi)存要糊。
*
...
*
* <p>Or use {@code new OkHttpClient.Builder()} to create a shared instance with custom settings:
* <pre> {@code
* 創(chuàng)建實(shí)例說(shuō)明,可以看到是通過(guò)Builder模式創(chuàng)建的
* // The singleton HTTP client.
* public final OkHttpClient client = new OkHttpClient.Builder()
* .addInterceptor(new HttpLoggingInterceptor())
* .cache(new Cache(cacheDir, cacheSize))
* .build();
* }</pre>
*
* <h3>Customize your client with newBuilder()</h3>
*
* 可以調(diào)用newBuilder方法來(lái)定制自己的client妆丘,調(diào)用后創(chuàng)建的client會(huì)保存上次的連接池和線程池以及之前一些配置
* <p>You can customize a shared OkHttpClient instance with {@link #newBuilder()}. This builds a client that shares the same connection pool, thread pools, and configuration. Use the builder methods to configure the derived client for a specific purpose.
*
* <p>This example shows a call with a short 500 millisecond timeout: <pre> {@code
*
* OkHttpClient eagerClient = client.newBuilder()
* .readTimeout(500, TimeUnit.MILLISECONDS)
* .build();
* Response response = eagerClient.newCall(request).execute();
* }</pre>
*
* ...
*/
OkHttpClient算是執(zhí)行調(diào)用請(qǐng)求Call的工廠锄俄,這個(gè)工廠將會(huì)被用來(lái)發(fā)送Http請(qǐng)求和讀取他們的返回這里強(qiáng)調(diào)OkHttp的使用最好創(chuàng)建一個(gè)單例OkHttpClient實(shí)例,并且重復(fù)使用勺拣。這是因?yàn)槊恳粋€(gè)Client都有自己的一個(gè)連接池connection pool和線程池thread pools奶赠。重用這些連接池和線程池可以減少延遲和節(jié)約內(nèi)存。
我們看下里面的源碼药有,由于使用Builder毅戈,看OkHttpClient的也就相當(dāng)于看Builder
public static final class Builder {
Dispatcher dispatcher; //調(diào)度器,里面包含了線程池和三個(gè)隊(duì)列(readyAsyncCalls:保存等待執(zhí)行的異步請(qǐng)求
Proxy proxy; //代理類(lèi)愤惰,默認(rèn)有三種代理模式DIRECT(直連),HTTP(http代理),SOCKS(socks代理)苇经,這三種模式,折騰過(guò)科學(xué)上網(wǎng)的或多或少都了解一點(diǎn)吧宦言。
List<Protocol> protocols; //協(xié)議集合扇单,協(xié)議類(lèi),用來(lái)表示使用的協(xié)議版本奠旺,比如`http/1.0,`http/1.1,`spdy/3.1,`h2等
List<ConnectionSpec> connectionSpecs; //連接規(guī)范蜘澜,用于配置Socket連接層。對(duì)于HTTPS凉倚,還能配置安全傳輸層協(xié)議(TLS)版本和密碼套件
final List<Interceptor> interceptors = new ArrayList<>(); //攔截器兼都,用來(lái)監(jiān)聽(tīng)請(qǐng)求
final List<Interceptor> networkInterceptors = new ArrayList<>();
ProxySelector proxySelector; //代理選擇類(lèi),默認(rèn)不使用代理稽寒,即使用直連方式,當(dāng)然趟章,我們可以自定義配置杏糙,以指定URI使用某種代理,類(lèi)似代理軟件的PAC功能蚓土。
CookieJar cookieJar; //Cookie的保存獲取
Cache cache; //緩存類(lèi)宏侍,內(nèi)部使用了DiskLruCache來(lái)進(jìn)行管理緩存,匹配緩存的機(jī)制不僅僅是根據(jù)url蜀漆,而且會(huì)根據(jù)請(qǐng)求方法和請(qǐng)求頭來(lái)驗(yàn)證是否可以響應(yīng)緩存谅河。此外,僅支持GET請(qǐng)求的緩存。
InternalCache internalCache; //內(nèi)置緩存
SocketFactory socketFactory; //Socket的抽象創(chuàng)建工廠绷耍,通過(guò)`createSocket來(lái)創(chuàng)建Socket
吐限。
SSLSocketFactory sslSocketFactory; //安全套接層工廠,HTTPS相關(guān)褂始,用于創(chuàng)建SSLSocket诸典。一般配置HTTPS證書(shū)信任問(wèn)題都需要從這里著手。對(duì)于不受信任的證書(shū)一般會(huì)提示javax.net.ssl.SSLHandshakeException異常崎苗。
CertificateChainCleaner certificateChainCleaner; //證書(shū)鏈清潔器狐粱,HTTPS相關(guān),用于從[Java]的TLS API構(gòu)建的原始數(shù)組中統(tǒng)計(jì)有效的證書(shū)鏈胆数,然后清除跟TLS握手不相關(guān)的證書(shū)肌蜻,提取可信任的證書(shū)以便可以受益于證書(shū)鎖機(jī)制。
HostnameVerifier hostnameVerifier; //主機(jī)名驗(yàn)證器必尼,與HTTPS中的SSL相關(guān)宋欺,當(dāng)握手時(shí)如果URL的主機(jī)名不是可識(shí)別的主機(jī),就會(huì)要求進(jìn)行主機(jī)名驗(yàn)證
CertificatePinner certificatePinner; // 證書(shū)鎖胰伍,HTTPS相關(guān)齿诞,用于約束哪些證書(shū)可以被信任,可以防止一些已知或未知的中間證書(shū)機(jī)構(gòu)帶來(lái)的攻擊行為骂租。如果所有證書(shū)都不被信任將拋出SSLPeerUnverifiedException異常祷杈。
Authenticator proxyAuthenticator; //身份認(rèn)證器,當(dāng)連接提示未授權(quán)時(shí)渗饮,可以通過(guò)重新設(shè)置請(qǐng)求頭來(lái)響應(yīng)一個(gè)新的Request但汞。狀態(tài)碼401表示遠(yuǎn)程服務(wù)器請(qǐng)求授權(quán),407表示代理服務(wù)器請(qǐng)求授權(quán)互站。該認(rèn)證器在需要時(shí)會(huì)被RetryAndFollowUpInterceptor觸發(fā)私蕾。
Authenticator authenticator;
ConnectionPool connectionPool; //連接池
Dns dns;
boolean followSslRedirects; //是否遵循SSL重定向
boolean followRedirects; //是否重定向
boolean retryOnConnectionFailure; //失敗是否重新連接
int connectTimeout; //連接超時(shí)
int readTimeout; //讀取超時(shí)
int writeTimeout; //寫(xiě)入超時(shí)
...
}
注釋中說(shuō)明的很清楚了
可以看到在OkHttpClient這里就設(shè)置了這么多的字段,常用的讀寫(xiě)時(shí)間胡桃,延遲請(qǐng)求踩叭,緩存都在這里設(shè)置了。
看下構(gòu)造器的賦值
public Builder() {
dispatcher = new Dispatcher();
protocols = DEFAULT_PROTOCOLS; //默認(rèn)支持的協(xié)議
connectionSpecs = DEFAULT_CONNECTION_SPECS; //默認(rèn)的連接規(guī)范
proxySelector = ProxySelector.getDefault(); //默認(rèn)的代理選擇器翠胰,直連
cookieJar = CookieJar.NO_COOKIES; //默認(rèn)不進(jìn)行管理Cookie
socketFactory = SocketFactory.getDefault();
hostnameVerifier = OkHostnameVerifier.INSTANCE; //主機(jī)驗(yàn)證
certificatePinner = CertificatePinner.DEFAULT; //證書(shū)鎖容贝,默認(rèn)不開(kāi)啟
proxyAuthenticator = Authenticator.NONE; //默認(rèn)不進(jìn)行授權(quán)
authenticator = Authenticator.NONE;
connectionPool = new ConnectionPool(); //連接池
dns = Dns.SYSTEM;
followSslRedirects = true;
followRedirects = true;
retryOnConnectionFailure = true;
//超時(shí)時(shí)間
connectTimeout = 10_000;
readTimeout = 10_000;
writeTimeout = 10_000;
}
Dispatcher
它是一個(gè)異步請(qǐng)求執(zhí)行政策,當(dāng)我們用OkHttpClient.newCall(request)進(jìn)行execute/enenqueue時(shí)之景,實(shí)際是將請(qǐng)求Call放到了Dispatcher中斤富,okhttp使用Dispatcher進(jìn)行線程分發(fā),它有兩種方法锻狗,一個(gè)是普通的同步單線程满力;另一種是使用了隊(duì)列進(jìn)行并發(fā)任務(wù)的分發(fā)(Dispatch)與回調(diào)焕参。另外,在Dispatcher中每一個(gè)請(qǐng)求都是使用 ExecutorService 來(lái)執(zhí)行的油额。
public final class Dispatcher {
private int maxRequests = 64; //最大并發(fā)數(shù)為64叠纷,同時(shí)請(qǐng)求
private int maxRequestsPerHost = 5; //每個(gè)主機(jī)的最大請(qǐng)求數(shù)為5
private Runnable idleCallback; //閑置接口
/** Executes calls. Created lazily. */
private ExecutorService executorService; //線程池
//緩存好的異步調(diào)用,都是放在隊(duì)列里保存
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
//運(yùn)行中的異步調(diào)用悔耘,都是放在隊(duì)列里保存
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
//運(yùn)行中的同步調(diào)用讲岁,都是放在隊(duì)列里保存
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
...
}
前面說(shuō)到,當(dāng)我們用OkHttpClient.newCall(request)進(jìn)行execute/enqueue時(shí)衬以,實(shí)際是將請(qǐng)求Call放到了Dispatcher中缓艳。
我們先看回之前的用法
Call call = okHttpClient.newCall(request);
//異步
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
//同步
try {
Response response = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
通過(guò)調(diào)用okHttpClient.newCall將請(qǐng)求Request構(gòu)造成Call,進(jìn)行發(fā)起請(qǐng)求看峻。
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
newCall這里則是創(chuàng)建了一個(gè)RealCall對(duì)象
Call
首先對(duì)于Call阶淘,大家比較熟悉,它是一個(gè)接口互妓,定義了各種Http連接請(qǐng)求的方法
public interface Call {
Request request();
...
Response execute() throws IOException;
void enqueue(Callback responseCallback);
void cancel();
boolean isExecuted();
boolean isCanceled();
interface Factory {
Call newCall(Request request);
}
}
可以通過(guò)request()方法獲取自己的請(qǐng)求體溪窒,調(diào)用enqueue發(fā)起異步請(qǐng)求,調(diào)用execute發(fā)起同步請(qǐng)求
RealCall
RealCall則是Call的實(shí)現(xiàn)類(lèi)
final class RealCall implements Call {
private final OkHttpClient client;
private final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
// Guarded by this.
private boolean executed;
/** The application's original request unadulterated by redirects or auth headers. */
Request originalRequest;
protected RealCall(OkHttpClient client, Request originalRequest) {
this.client = client;
this.originalRequest = originalRequest;
this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client);
}
@Override public Request request() {
return originalRequest;
}
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
...
}
RealCall中實(shí)現(xiàn)了Execute和enqueue等方法冯勉。而在RealCall的execute和enqueue方法中都調(diào)用到了dispatcher.enqueue/execute澈蚌。
我們先看下同步方法RealCall.execute
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} finally {
client.dispatcher().finished(this);
}
}
同步方法中做了4件事
- 檢查這個(gè) call 是否已經(jīng)被執(zhí)行了,每個(gè) call 只能被執(zhí)行一次灼狰,如果想要一個(gè)完全一樣的 call宛瞄,可以利用
call#clone
方法進(jìn)行克隆。 - 利用
client.dispatcher().executed(this)
來(lái)進(jìn)行實(shí)際執(zhí)行交胚,dispatcher
是剛才看到的OkHttpClient.Builder
的成員之一份汗,它的文檔說(shuō)自己是異步 HTTP 請(qǐng)求的執(zhí)行策略,現(xiàn)在看來(lái)蝴簇,同步請(qǐng)求它也有摻和杯活。 - 調(diào)用
getResponseWithInterceptorChain()
函數(shù)獲取 HTTP 返回結(jié)果,從函數(shù)名可以看出熬词,這一步還會(huì)進(jìn)行一系列“攔截”操作旁钧。 - 最后還要通知
dispatcher
自己已經(jīng)執(zhí)行完畢。
dispatcher 這里我們不過(guò)度關(guān)注荡澎,在同步執(zhí)行的流程中均践,涉及到 dispatcher 的內(nèi)容只不過(guò)是告知它我們的執(zhí)行狀態(tài),比如開(kāi)始執(zhí)行了(調(diào)用 executed
)摩幔,比如執(zhí)行完畢了(調(diào)用 finished
),在異步執(zhí)行流程中它會(huì)有更多的參與鞭铆。
這里同步請(qǐng)求或衡,只是把當(dāng)前請(qǐng)求添加到隊(duì)列而已
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
真正發(fā)出網(wǎng)絡(luò)請(qǐng)求焦影,解析返回結(jié)果的,還是 getResponseWithInterceptorChain
封断,這個(gè)下面說(shuō)斯辰,最后再調(diào)用了dispatch.finish方法
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();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
在finish方法中,會(huì)把當(dāng)前請(qǐng)求從running隊(duì)列中移除坡疼,然后把緩存隊(duì)列的請(qǐng)求添加到running隊(duì)列彬呻。
接下來(lái)看下異步請(qǐng)求
RealCall.enqueue
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
先判斷當(dāng)前Call是否在執(zhí)行,再調(diào)用dispatch.enqueue方法
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
方法中柄瑰,這里先有個(gè)判斷闸氮,如果當(dāng)前運(yùn)行的異步請(qǐng)求隊(duì)列長(zhǎng)度小于最大請(qǐng)求數(shù),也就是64,并且主機(jī)的請(qǐng)求數(shù)小于每個(gè)主機(jī)的請(qǐng)求數(shù)也就是5,則把當(dāng)前請(qǐng)求添加到 運(yùn)行隊(duì)列教沾,接著交給線程池ExecutorService處理蒲跨,否則則放置到readAsyncCall進(jìn)行緩存,等待執(zhí)行授翻。
可以看到同步與異步一點(diǎn)區(qū)別就是或悲,異步的執(zhí)行交給了線程池去操作。
我們看下OkHttp里面的線程池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;
}
這里則是通過(guò)ThreadPoolExecutor來(lái)創(chuàng)建線程池
參數(shù)說(shuō)明如下:
int corePoolSize: 最小并發(fā)線程數(shù)堪唐,這里并發(fā)同時(shí)包括空閑與活動(dòng)的線程巡语,如果是0的話,空閑一段時(shí)間后所有線程將全部被銷(xiāo)毀淮菠。
int maximumPoolSize: 最大線程數(shù)男公,當(dāng)任務(wù)進(jìn)來(lái)時(shí)可以擴(kuò)充的線程最大值,當(dāng)大于了這個(gè)值就會(huì)根據(jù)丟棄處理機(jī)制來(lái)處理
long keepAliveTime: 當(dāng)線程數(shù)大于
corePoolSize
時(shí)兜材,多余的空閑線程的最大存活時(shí)間理澎,類(lèi)似于HTTP中的Keep-aliveTimeUnit unit: 時(shí)間單位,一般用秒
BlockingQueue<Runnable> workQueue: 工作隊(duì)列曙寡,先進(jìn)先出糠爬,可以看出并不像Picasso那樣設(shè)置優(yōu)先隊(duì)列。
ThreadFactory threadFactory: 單個(gè)線程的工廠举庶,可以打Log执隧,設(shè)置
Daemon
(即當(dāng)JVM退出時(shí),線程自動(dòng)結(jié)束)等
可以看出户侥,在Okhttp中镀琉,構(gòu)建了一個(gè)閥值為[0, Integer.MAX_VALUE]的線程池,它不保留任何最小線程數(shù)蕊唐,隨時(shí)創(chuàng)建更多的線程數(shù)屋摔,當(dāng)線程空閑時(shí)只能活60秒,它使用了一個(gè)不存儲(chǔ)元素的阻塞工作隊(duì)列替梨,一個(gè)叫做"OkHttp Dispatcher"的線程工廠钓试。
也就是說(shuō)装黑,在實(shí)際運(yùn)行中,當(dāng)收到10個(gè)并發(fā)請(qǐng)求時(shí)弓熏,線程池會(huì)創(chuàng)建十個(gè)線程恋谭,當(dāng)工作完成后,線程池會(huì)在60s后相繼關(guān)閉所有線程挽鞠。
添加到線程池后就交給ThreadPoolExecutor去調(diào)用疚颊,最終則是調(diào)用到我們的請(qǐng)求AsyncCall的execute方法⌒湃希回看上面的代碼材义,異步請(qǐng)求中,我們傳遞了個(gè)Callback接口進(jìn)來(lái)狮杨,而在RealCall的enqueue方法中母截,Callback回調(diào)接口被封裝到AsyncCall中,而AsyncCall繼承與NamedRunnable橄教,而NamaedRunnable則實(shí)現(xiàn)了Runnable方法清寇。
AsyncCall
AsyncCall繼承于NamedRunnable,而NamaedRunnable則實(shí)現(xiàn)了Runnable方法
final class AsyncCall extends NamedRunnable {
private final Callback responseCallback;
private AsyncCall(Callback responseCallback) {
super("OkHttp %s", redactedUrl().toString());
this.responseCallback = responseCallback;
}
String host() {
return originalRequest.url().host();
}
Request request() {
return originalRequest;
}
RealCall get() {
return RealCall.this;
}
@Override protected void execute() {
boolean signalledCallback = false;
try {
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);
}
}
}
可以看到在它的構(gòu)造器中护蝶,Callback就是我們?cè)O(shè)置的創(chuàng)建的华烟,帶有onFail和onResponse方法。
而線程池中最終調(diào)用到的則是我們的Runnable持灰。
這里通過(guò)
Response response = getResponseWithInterceptorChain();
方法來(lái)進(jìn)行連接訪問(wèn)盔夜,這里跟同步請(qǐng)求一樣。最后根據(jù)返回值調(diào)用callback.onFailure/onResponse
我們關(guān)鍵還是看OkHttp如何連接返回的堤魁,我們看下這個(gè)方法
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);
}
可以看到這里全都是關(guān)于Interceptor攔截器的使用
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;
Connection connection();
}
}
Interceptor
是 OkHttp 最核心的一個(gè)東西喂链,不要誤以為它只負(fù)責(zé)攔截請(qǐng)求進(jìn)行一些額外的處理(例如 cookie),實(shí)際上它把實(shí)際的網(wǎng)絡(luò)請(qǐng)求妥泉、緩存椭微、透明壓縮等功能都統(tǒng)一了起來(lái),每一個(gè)功能都只是一個(gè) Interceptor
盲链,它們?cè)龠B接成一個(gè) Interceptor.Chain
蝇率,環(huán)環(huán)相扣,最終圓滿完成一次網(wǎng)絡(luò)請(qǐng)求刽沾。
從 getResponseWithInterceptorChain
函數(shù)我們可以看到本慕,Interceptor.Chain
的分布依次是:
在配置
OkHttpClient
時(shí)設(shè)置的interceptors
;負(fù)責(zé)失敗重試以及重定向的
RetryAndFollowUpInterceptor
侧漓;負(fù)責(zé)把用戶(hù)構(gòu)造的請(qǐng)求轉(zhuǎn)換為發(fā)送到服務(wù)器的請(qǐng)求锅尘、把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶(hù)友好的響應(yīng)的
BridgeInterceptor
;負(fù)責(zé)讀取緩存直接返回布蔗、更新緩存的
CacheInterceptor
鉴象;負(fù)責(zé)和服務(wù)器建立連接的
ConnectInterceptor
忙菠;配置
OkHttpClient
時(shí)設(shè)置的networkInterceptors
何鸡;負(fù)責(zé)向服務(wù)器發(fā)送請(qǐng)求數(shù)據(jù)纺弊、從服務(wù)器讀取響應(yīng)數(shù)據(jù)的
CallServerInterceptor
。
[責(zé)任鏈模式]在這個(gè) Interceptor
鏈條中得到了很好的實(shí)踐
在這里骡男,位置決定了功能淆游,最后一個(gè) Interceptor 一定是負(fù)責(zé)和服務(wù)器實(shí)際通訊的,重定向隔盛、緩存等一定是在實(shí)際通訊之前的犹菱。
對(duì)于把 Request
變成 Response
這件事來(lái)說(shuō),每個(gè) Interceptor
都可能完成這件事吮炕,所以我們循著鏈條讓每個(gè) Interceptor
自行決定能否完成任務(wù)以及怎么完成任務(wù)(自力更生或者交給下一個(gè)Interceptor
)腊脱。這樣一來(lái),完成網(wǎng)絡(luò)請(qǐng)求這件事就徹底從 RealCall
類(lèi)中剝離了出來(lái)龙亲,簡(jiǎn)化了各自的責(zé)任和邏輯陕凹。
講解其他攔截器前,先認(rèn)識(shí)幾個(gè)類(lèi)
HttpStream
public interface HttpStream {
//超時(shí)漸漸
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
//返回一個(gè)output stream(如果RequestBody可以轉(zhuǎn)為流)
Sink createRequestBody(Request request, long contentLength);
//寫(xiě)入請(qǐng)求頭
void writeRequestHeaders(Request request) throws IOException;
// Flush the request
void finishRequest() throws IOException;
//讀取請(qǐng)求頭
Response.Builder readResponseHeaders() throws IOException;
//返回ResponseBody
ResponseBody openResponseBody(Response response) throws IOException;
void cancel();
}
可以看到HttpStream是一個(gè)接口鳄炉,里面提供了很多類(lèi)似的流操作杜耙,比如Sink。
而HttpStream對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)有Http1xStream拂盯、Http2xStream佑女。分別對(duì)應(yīng)HTTP/1.1、HTTP/2和SPDY協(xié)議谈竿。我們可以大約知道团驱,通過(guò)writeRequestHeaders
開(kāi)始寫(xiě)入請(qǐng)求頭到服務(wù)器,createRequestBody
用于獲取寫(xiě)入流來(lái)寫(xiě)入請(qǐng)求體空凸。readResponseHeaders
用于讀取響應(yīng)頭嚎花,openResponseBody
用于打開(kāi)一個(gè)響應(yīng)體。關(guān)于相應(yīng)實(shí)現(xiàn)的源碼這里就不分析了,比較簡(jiǎn)單劳秋,無(wú)非就是讀寫(xiě)操作吸申。
StreamAllocation
流分配器,該類(lèi)用于協(xié)調(diào)連接丛楚、流和請(qǐng)求三者之間的關(guān)系。通過(guò)調(diào)用newStream
可以獲取一個(gè)HttpStream實(shí)現(xiàn)
public HttpStream newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
//獲取設(shè)置的超時(shí)時(shí)間
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpStream resultStream;
if (resultConnection.framedConnection != null) {
resultStream = new Http2xStream(client, this, resultConnection.framedConnection);
} else {
resultConnection.socket().setSoTimeout(readTimeout);
resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
resultStream = new Http1xStream(
client, this, resultConnection.source, resultConnection.sink);
}
synchronized (connectionPool) {
stream = resultStream;
return resultStream;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
可以看到在newStream這里憔辫,通過(guò)RealConnection建立Socket連接趣些,接著獲取連接對(duì)應(yīng)的流。
而在RealConnection的connectSocket方法中
private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
Proxy proxy = route.proxy();
Address address = route.address();
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
rawSocket.setSoTimeout(readTimeout);
try {
Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
} catch (ConnectException e) {
throw new ConnectException("Failed to connect to " + route.socketAddress());
}
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
}
可以看到Socket和Okio的連接使用
重試與重定向攔截器 RetryAndFollowUpInterceptor
用來(lái)實(shí)現(xiàn)重試和重定向功能
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()));
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException();
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, false, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//獲取重定向信息
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
//判斷是否需要重定向
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(followUp.url()));
} else if (streamAllocation.stream() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
RetryAndFollowUpInterceptor在intercept()中首先從client取得connection pool贰您,用所請(qǐng)求的URL創(chuàng)建Address對(duì)象坏平,并以此創(chuàng)建StreamAllocation對(duì)象拢操。
Address描述某一個(gè)特定的服務(wù)器地址。StreamAllocation對(duì)象則用于分配一個(gè)到特定的服務(wù)器地址的流HttpStream舶替,這個(gè)HttpStream可能是從connection pool中取得的之前沒(méi)有釋放的連接令境,也可能是重新分配的。RetryAndFollowUpInterceptor這里算是為后面的操作準(zhǔn)備執(zhí)行條件StreamAllocation顾瞪。
隨后利用Interceptor鏈中后面的Interceptor來(lái)獲取網(wǎng)絡(luò)響應(yīng)舔庶。并檢查是否為重定向響應(yīng)。若不是就將響應(yīng)返回陈醒,若是則做進(jìn)一步處理惕橙。
對(duì)于重定向的響應(yīng),RetryAndFollowUpInterceptor.intercept()會(huì)利用響應(yīng)的信息創(chuàng)建一個(gè)新的請(qǐng)求钉跷。并檢查新請(qǐng)求的服務(wù)器地址與老地址是否相同弥鹦,若不相同則會(huì)根據(jù)新的地址創(chuàng)建Address對(duì)象及StreamAllocation對(duì)象。
RetryAndFollowUpInterceptor對(duì)重定向的響應(yīng)也不會(huì)無(wú)休止的處理下去爷辙,它處理的最多的重定向級(jí)數(shù)為20次彬坏,超過(guò)20次時(shí),它會(huì)拋異常出來(lái)犬钢。
RetryAndFollowUpInterceptor通過(guò)followUpRequest()從響應(yīng)的信息中提取出重定向的信息苍鲜,接著通過(guò)sameConnection來(lái)判斷是否需要重定向連接
RetryAndFollowUpInterceptor主要做了
創(chuàng)建StreamAllocation,以此傳入到后續(xù)的Interceptor中
處理重定向的Http響應(yīng)
橋接攔截器 BridgeInterceptor
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
requestBuilder.header("Content-Length", Long.toString(contentLength));
requestBuilder.removeHeader("Transfer-Encoding");
} else {
requestBuilder.header("Transfer-Encoding", "chunked");
requestBuilder.removeHeader("Content-Length");
}
}
...
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build());
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
可以看到在BridgeInterceptor中玷犹,主要用于用于完善請(qǐng)求頭混滔,比如Content-Type、Content-Length歹颓、Host坯屿、Connection、Accept-Encoding巍扛、User-Agent等等领跛,這些請(qǐng)求頭不用用戶(hù)一一設(shè)置,如果用戶(hù)沒(méi)有設(shè)置該庫(kù)會(huì)檢查并自動(dòng)完善撤奸。此外吠昭,這里會(huì)進(jìn)行加載和回調(diào)cookie。
緩存攔截器 CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
//根據(jù)Request獲取緩存中的Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根據(jù)請(qǐng)求頭獲取用戶(hù)指定的緩存策略胧瓜,并根據(jù)緩存策略來(lái)獲取networkRequest矢棚,cacheResponse。
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
//如果不需要網(wǎng)絡(luò)則直接返回從緩存中讀取的Response
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
}
...
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (validate(cacheResponse, networkResponse)) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
緩存攔截器府喳,首先根據(jù)Request中獲取緩存的Response蒲肋,然后根據(jù)用于設(shè)置的緩存策略來(lái)進(jìn)一步判斷緩存的Response是否可用以及是否發(fā)送網(wǎng)絡(luò)請(qǐng)求。如果從網(wǎng)絡(luò)中讀取,此時(shí)再次根據(jù)緩存策略來(lái)決定是否緩存響應(yīng)兜粘。
這塊代碼比較多申窘,但也很直觀,主要涉及 HTTP 協(xié)議緩存細(xì)節(jié)的實(shí)現(xiàn)孔轴,而具體的緩存邏輯 OkHttp 內(nèi)置封裝了一個(gè) Cache 類(lèi)剃法,它利用 DiskLruCache,用磁盤(pán)上的有限大小空間進(jìn)行緩存距糖,按照 LRU 算法進(jìn)行緩存淘汰玄窝,這里也不再展開(kāi)。
我們可以在構(gòu)造 OkHttpClient 時(shí)設(shè)置 Cache 對(duì)象悍引,在其構(gòu)造函數(shù)中我們可以指定目錄和緩存大小:
public Cache(File directory, long maxSize);
而如果我們對(duì) OkHttp 內(nèi)置的 Cache 類(lèi)不滿意帽氓,我們可以自行實(shí)現(xiàn) InternalCache 內(nèi)置緩存接口趣斤,在構(gòu)造OkHttpClient 時(shí)進(jìn)行設(shè)置,這樣就可以使用我們自定義的緩存策略了黎休。
建立連接 ConnectInterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpStream, connection);
}
可以看到浓领,在ConnectInterceptor獲取到StreamAllocation,而StreamAllocation的創(chuàng)建則是在 RetryAndFollowUpInterceptor重定向攔截器這里势腮。
接著調(diào)用到了streamAllocation.newStream联贩,前面介紹到,在newStream方法中會(huì)通過(guò)RealConnection建立與服務(wù)器之間的連接
實(shí)際上建立連接就是創(chuàng)建了一個(gè) HttpCodec 對(duì)象捎拯,它將在后面的步驟中被使用泪幌,那它又是何方神圣呢?它是對(duì) HTTP 協(xié)議操作的抽象署照,有兩個(gè)實(shí)現(xiàn):Http1Codec 和 Http2Codec祸泪,顧名思義,它們分別對(duì)應(yīng) HTTP/1.1 和 HTTP/2 版本的實(shí)現(xiàn)建芙。
在 Http1Codec 中没隘,它利用 Okio 對(duì) Socket 的讀寫(xiě)操作進(jìn)行封裝,Okio 以后有機(jī)會(huì)再進(jìn)行分析禁荸,現(xiàn)在讓我們對(duì)它們保持一個(gè)簡(jiǎn)單地認(rèn)識(shí):它對(duì) java.io 和 java.nio 進(jìn)行了封裝右蒲,讓我們更便捷高效的進(jìn)行 IO 操作。
發(fā)送和接收數(shù)據(jù) CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpStream.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpStream.finishRequest();
Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
int code = response.code();
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
我們抓住主干部分:
- 向服務(wù)器發(fā)送 request header赶熟;
- 如果有 request body瑰妄,就向服務(wù)器發(fā)送;
- 讀取 response header钧大,先構(gòu)造一個(gè) Response 對(duì)象翰撑;
- 如果有 response body,就在 3 的基礎(chǔ)上加上 body 構(gòu)造一個(gè)新的 Response 對(duì)象;
CallServerInterceptor首先將http請(qǐng)求頭部發(fā)給服務(wù)器眶诈,如果http請(qǐng)求有body的話涨醋,會(huì)再將body發(fā)送給服務(wù)器,繼而通過(guò)httpStream.finishRequest()結(jié)束http請(qǐng)求的發(fā)送逝撬。
請(qǐng)求完成之后浴骂,我們就可以從 Response 對(duì)象中獲取到響應(yīng)數(shù)據(jù)了,包括 HTTP status code宪潮,status message溯警,response header,response body 等狡相。這里 body 部分最為特殊梯轻,因?yàn)榉?wù)器返回的數(shù)據(jù)可能非常大,所以必須通過(guò)數(shù)據(jù)流的方式來(lái)進(jìn)行訪問(wèn)(當(dāng)然也提供了諸如 string() 和 bytes() 這樣的方法將流內(nèi)的數(shù)據(jù)一次性讀取完畢)尽棕,而響應(yīng)中其他部分則可以隨意獲取喳挑。
響應(yīng) body 被封裝到 ResponseBody 類(lèi)中,該類(lèi)主要有兩點(diǎn)需要注意:
- 每個(gè) body 只能被消費(fèi)一次滔悉,多次消費(fèi)會(huì)拋出異常伊诵;
- body 必須被關(guān)閉,否則會(huì)發(fā)生資源泄漏回官;
小結(jié)
RetryAndFollowUpInterceptor : 創(chuàng)建StreamAllocation對(duì)象曹宴,處理http的重定向,出錯(cuò)重試歉提。對(duì)后續(xù)Interceptor的執(zhí)行的影響:修改request及StreamAllocation笛坦。
BridgeInterceptor:補(bǔ)全缺失的一些http header,Cookie設(shè)置唯袄。對(duì)后續(xù)Interceptor的執(zhí)行的影響:修改request弯屈。
CacheInterceptor:處理http緩存。對(duì)后續(xù)Interceptor的執(zhí)行的影響:若緩存中有所需請(qǐng)求的響應(yīng)恋拷,則后續(xù)Interceptor不再執(zhí)行资厉。
ConnectInterceptor:借助于前面分配的StreamAllocation對(duì)象建立與服務(wù)器之間的連接(具體建立是在newStream方法中),并選定交互所用的協(xié)議是HTTP 1.1還是HTTP 2蔬顾。對(duì)后續(xù)Interceptor的執(zhí)行的影響:創(chuàng)建了httpStream和connection宴偿。
CallServerInterceptor:處理IO,與服務(wù)器進(jìn)行數(shù)據(jù)交換诀豁。對(duì)后續(xù)Interceptor的執(zhí)行的影響:為Interceptor鏈中的最后一個(gè)Interceptor窄刘,沒(méi)有后續(xù)Interceptor。
在文章最后我們?cè)賮?lái)回顧一下完整的流程圖: