1. 基本使用
1.1 創(chuàng)建 OkHttpClient
首先創(chuàng)建 OkHttpClient 用于配置網(wǎng)絡(luò)請求時連接時長,讀/寫數(shù)據(jù)時長,緩存路徑等參數(shù)信息:
OkHttpClient mOkHttpClient;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS) //連接最大時長
.writeTimeout(20, TimeUnit.SECONDS) //客戶端寫數(shù)據(jù)最大時長
.readTimeout(20, TimeUnit.SECONDS) //服務(wù)端讀數(shù)據(jù)最大時長
.cache(new Cache(sdCache.getAbsoluteFile(), cacheSize)); //配置緩存路徑即緩存大小限制
mOkHttpClient = builder.build();
1.2 創(chuàng)建 Request
創(chuàng)建 Request 用于設(shè)置連接的地址 url,請求方法(post/get),請求頭等信息:
//get請求
Request request = new Request.Builder()
.method("GET",null)
.url(url)
.build();
//post請求
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.build();
1.3 創(chuàng)建 Call
通過 OkHttpClient 和 Request 創(chuàng)建 Call 用于處理請求的回調(diào):
Call call = mOkHttpClient.newCall(request);
1.4 發(fā)起請求
-
同步請求
try {
final Response execute = call.execute();
} catch (IOException e) {
e.printStackTrace();
}
-
異步請求
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
//處理請求失敗
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
//處理請求成功
}
});
2. 源碼流程分析

2.1 同步請求
同步請求直接執(zhí)行 RealCall 的 execute() 方法,然后調(diào)用調(diào)度器 Dispatcher 的 execute() 方法將當(dāng)前同步請求加入同步請求隊(duì)列 runningSyncCalls 中,接著調(diào)用 getResponseWithInterceptorChain() 方法進(jìn)行攔截器的鏈?zhǔn)秸{(diào)用。
2.2 異步請求
異步請求會執(zhí)行 RealCall 的 enqueue() 方法,然后通過調(diào)度器 Dispatcher 調(diào)度異步請求,調(diào)度器中有三個隊(duì)列 readyAsyncCalls、runningAsyncCalls、runningSyncCalls 分別用于存儲將要執(zhí)行的異步請求、正在執(zhí)行的異步請求以及正在執(zhí)行的同步請求,如果當(dāng)前正在執(zhí)行的最大請求數(shù)小于最大請求數(shù) maxRequests(默認(rèn)為64)且未達(dá)到同一個主機(jī)名的最大請求數(shù) maxRequestsPerHost(默認(rèn)為5)則將當(dāng)前異步請求加入正在執(zhí)行的異步請求隊(duì)列 runningAsyncCalls 中,然后通過線程池執(zhí)行當(dāng)前異步請求。
異步請求執(zhí)行的是 RealCall 的內(nèi)部類 AsyncCall 的 run() 方法,該方法中調(diào)用 getResponseWithInterceptorChain() 方法進(jìn)行攔截器的鏈?zhǔn)秸{(diào)用。
2.3 總結(jié):
- 不同:同步請求是在當(dāng)前線程執(zhí)行,而異步請求會在線程池中使用子線程執(zhí)行;
- 相同:相同的是同步請求和異步請求都會調(diào)用
getResponseWithInterceptorChain()方法進(jìn)行攔截器的鏈?zhǔn)秸{(diào)用實(shí)現(xiàn)發(fā)起請求、失敗重連、處理緩存、建立網(wǎng)絡(luò)連接、接受響應(yīng)等任務(wù)。
3. 攔截器
3.1 RetryAndFollowUpInterceptor
實(shí)現(xiàn)失敗重連和重定向的請求:

-
注意
RetryAndFollowUpInterceptor 之前的攔截器 interceptors,在客戶端發(fā)起請求后只會被調(diào)用一次,而 RetryAndFollowUpInterceptor 之后添加的攔截器,比如 BridgeInterceptor、CacheInterceptor、ConnectInterceptor、networkInterceptor、CallServerInterceptor 在重定向或者重連時都會重復(fù)調(diào)用,這也是OkHttpClient 中 interceptors 和 networkInterceptor 兩類攔截器的區(qū)別 。
3.2 BridgeInterceptor
將用戶構(gòu)造的請求轉(zhuǎn)換為發(fā)送到服務(wù)器的請求,把服務(wù)器返回的響應(yīng)轉(zhuǎn)換為用戶友好的請求,是從程序代碼到網(wǎng)絡(luò)代碼的橋梁,主要實(shí)現(xiàn)了請 求頭 header 的封裝和響應(yīng)內(nèi)容的解壓:
- 設(shè)置請求內(nèi)容類型: Content-Type、內(nèi)容長度:Content-Length以及請求內(nèi)容編碼格式:Transfer-Encoding;
- 設(shè)置 Host、User-Agent ,設(shè)置 Connection 為 Keep-Alive;
- 添加 Cookie;
- 設(shè)置接受內(nèi)容編碼格式為
gzip,并在接受到響應(yīng)內(nèi)容后進(jìn)行解壓,省去了用戶處理數(shù)據(jù)的麻煩;
3.3 CacheInterceptor
OkHttp 的緩存使用的是 DiskLruCache,在 CacheInterceptor 的攔截方法 intercept() 中如果在 OkHttpClient 中配置了緩存,首先會從磁盤中獲取當(dāng)前請求的緩存 Cache;
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null; //從磁盤文件中返回當(dāng)前請求的緩存
long now = System.currentTimeMillis();
//創(chuàng)建緩存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
// 不進(jìn)行網(wǎng)絡(luò)請求也不使用緩存
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
...
.build();
}
// 不使用網(wǎng)絡(luò)請求,使用緩存,直接返回緩存
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//開始網(wǎng)絡(luò)請求
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 網(wǎng)絡(luò)請求和緩存都使用,返回的請求碼為 304,表示重定向
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//根據(jù) cacheResponse 構(gòu)建新的響應(yīng),將 networkResponse 合并進(jìn)來
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
...
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// 更新緩存 Cache
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response; //返回由 cacheResponse 和 networkResponse 合并的響應(yīng)
} else {
closeQuietly(cacheResponse.body());
}
}
//不使用緩存,根據(jù) networkResponse 構(gòu)建響應(yīng),將 cacheResponse 合并進(jìn)來
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) { //磁盤中有該請求的緩存文件
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 存入緩存 Cache
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//請求方法不可使用緩存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest); //移除緩存
} catch (IOException ignored) {
}
}
}
return response;
}
- 根據(jù)當(dāng)前時間、Request、從磁盤中獲取的緩存, 創(chuàng)建一個緩存策略
CacheStrategy;
緩存策略包含 networkRequest 和 cacheResponse 兩個變量,可用來判斷本次請求的響應(yīng)內(nèi)容是由網(wǎng)絡(luò)請求返回還是使用緩存內(nèi)容,還是兩者都使用,networkRequest 為空表示不使用網(wǎng)絡(luò)請求,cacheResponse 為空表示不使用緩存。
- 緩存策略中不使用網(wǎng)絡(luò)請求也不使用緩存,創(chuàng)建一個包含異常信息的 Response并返回,注意返回碼為
504; - 不使用網(wǎng)絡(luò)請求,但使用緩存直接返回緩存;
- 需要使用網(wǎng)絡(luò)請求,調(diào)用 chain.proceed() 執(zhí)行后續(xù)攔截器進(jìn)行網(wǎng)絡(luò)請求,如果緩存策略中有 cacheResponse 且網(wǎng)絡(luò)請求返回碼為
304(表明是重定向請求),使用緩存 cacheResponse 構(gòu)建 Response,更新緩存 Cache 并返回 Response; - cacheResponse 為空,即不使用緩存,構(gòu)建網(wǎng)絡(luò)請求的 Response;
- 該請求在磁盤上有緩存即 Cache 不為空,把這個 Response 寫入緩存并返回。
3.4 ConnectInterceptor
建立客戶端和服務(wù)器之間的連接,為客戶端和服務(wù)器之間的通信做準(zhǔn)備。
-
StreamAllocation
- 作用:協(xié)調(diào) Connections、Streams、Calls 三個類之間的關(guān)系;
- newStream() 創(chuàng)建一個新的 Stream;
- findConnection():找到一個
RealConnection用于管理新的 Stream:
a. 如果已經(jīng)分配了連接,返回已分配的連接;
b. 沒有分配過連接則從連接池ConnectionPool中返回一個可用的連接(這里的可用即和當(dāng)前連接的 Adress主機(jī)名一致的連接 );
c. 沒有可用的連接,為當(dāng)前請求創(chuàng)建一個 Route;
d. 此時有了 Route,第二次從連接池 ConnectionPool 中找到一個匹配的連接并返回(在第一次基礎(chǔ)上加上對 Route 的判斷);
e. 如果仍然沒有可用的連接,直接新建一個連接RealConnection;
f . 創(chuàng)建的新連接通過 RealConnection#connect() 執(zhí)行TCP+TLS 握手;
g. 將這個新的連接存入連接池中,如果這個新的連接是多路復(fù)用(HTTP2.0支持多路復(fù)用,即多個請求可共用一個連接)的且和當(dāng)前連接是連接的同一個地址,釋放這個多路復(fù)用的連接并返回當(dāng)前連接; - 回到 newStream() 中,根據(jù) findConnection() 返回的 RealConnection 創(chuàng)建一個新的
HttpCodec即 HTTP編解碼器,用于編碼HTTP請求和解碼HTTP響應(yīng);
-
HttpCodec
HttpCodec 是一個接口,它有兩個實(shí)現(xiàn)類 Http1Codec 和 Http2Codec:
- Http1Codec:基于 HTTP1.1協(xié)議,實(shí)現(xiàn) Socket 連接并通信;
- Http2Codec:基于 HTTP2.0協(xié)議,實(shí)現(xiàn)編碼請求和解碼響應(yīng);
-
RealConnection
真正實(shí)現(xiàn)連接建立的類,調(diào)用 connect() 建立連接,下圖是連接建立的流程分析,從圖中可以看出,OkHttp 是使用 Socket 進(jìn)行網(wǎng)絡(luò)通信的,首先判斷是否有連接到代理服務(wù)器,有則創(chuàng)建代理請求然后連接到代理的服務(wù)器,沒有則直接連接到原始的服務(wù)器;接著根據(jù)連接地址 URL 是 http 還是 https,如果是 https 需要建立 tls 連接進(jìn)行身份驗(yàn)證:
RealConnection連接建立流程.png http 和 https 的區(qū)別:
https 需要配證書,ssl 層用于驗(yàn)證證書,tls 是 ssl 3.0 以后的版本,可以認(rèn)為是 ssl 3.1。

-
ConnectionPool
連接池,通過一個隊(duì)列Deque<RealConnection> connections存儲所有的已創(chuàng)建的連接,實(shí)現(xiàn)連接的復(fù)用,如果多個請求是請求的同一個主機(jī)地址,就不需要重復(fù)創(chuàng)建連接(三次握手),直接使用連接池中已有的指向同一個主機(jī)地址的連接;
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {...}
構(gòu)造方法中指定了最大空閑連接數(shù) maxIdleConnections 默認(rèn)為 5,以及連接的最大存活時長 keepAliveDurationNs 默認(rèn)為 5 分鐘,連接池中連接的清理工作交給了線程池去處理;
- put():存入一個新的連接,存入連接之前會通過
線程池清理掉不必要的連接;
void put(RealConnection connection) {
assert (Thread.holdsLock(this));
if (!cleanupRunning) {
cleanupRunning = true;
executor.execute(cleanupRunnable);
}
connections.add(connection);
}
- cleanup():清理不必要的連接,這里的不必要是指超出了最大空閑連接數(shù) maxIdleConnections 或者超出了連接存活時長 keepAliveDurationNs 的連接;
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++; //記錄正在使用的連接數(shù)
continue; //結(jié)束本次循環(huán)操作
}
idleConnectionCount++; //記錄空閑連接數(shù)
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) { //記錄空閑時長最長的連接
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
//拿到的這條連接空閑時長大于了連接默認(rèn)存活的最長時間或者空閑連接數(shù)大于了最大空閑連接數(shù)
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
connections.remove(longestIdleConnection); //從連接隊(duì)列中清除這條連接
} else if (idleConnectionCount > 0) { //空閑連接還沒達(dá)到最大存活時長,等待一段時間后再清除
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {//所有連接都在使用中,等待5分鐘后再處理清除操作
return keepAliveDurationNs;
} else {//連接隊(duì)列中沒有連接,不需要清理
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
a. 遍歷連接隊(duì)列,記錄空閑連接數(shù),找到連接隊(duì)列中空閑時長最長的連接;
b. 如果空閑連接數(shù)大于了 maxIdleConnections 或者連接存活時間大于了 keepAliveDurationNs,從連接隊(duì)列中移除這個空閑連接;
c. 有空閑連接但沒必要清除,等待一段時間(達(dá)到最大存活時間)再清除;
d. 所有連接都在使用中,5分鐘 后再清理;
- get():從連接隊(duì)列中返回一個
Adress的主機(jī)名一致或者Route匹配的連接,沒有則返回為null;
3.5 CallServerInterceptor
通過 HttpCodec 實(shí)現(xiàn)客戶端和服務(wù)器之間的通信:
-
writeRequestHeaders()向服務(wù)器發(fā)送 Request 的 header; - 如果有 body 通過
createRequestBody(),向服務(wù)器發(fā)送 body; -
readResponseHeaders(),讀取服務(wù)器返回的 header 并構(gòu)造一個新的 Response,構(gòu)造 Response 時斷開客戶端和服務(wù)器的連接; - 如果服務(wù)器返回的 Response 中有 body,通過
openResponseBody()讀取返回的 body,在步驟3 的 Response 基礎(chǔ)上加上這里的 body 并構(gòu)建一個新的 Response 。
4. 總結(jié):
OkHttp 具有以下優(yōu)勢:
- 失敗自動重連:在 RetryAndFollowUpInterceptor 失敗重連重定向攔截器中,連接失敗時會自動嘗試重新連接,也可以處理訪問的重定向;
- 可以解壓編碼類型為 gzip 的響應(yīng):在 BridgeInterceptor 橋接攔截器中默認(rèn)支持解壓編碼類型為 gzip 的響應(yīng);
- 支持緩存:在 CacheInterceptor 緩存攔截器中,使用緩存避免頻繁的重復(fù)請求;
- 連接可復(fù)用:在 ConnectionPool 中實(shí)現(xiàn)連接復(fù)用,避免頻繁創(chuàng)建和斷開連接;
- OkHttp 使用 Socket 發(fā)送請求:Socket 由 RealConnection 維護(hù);
- 同主機(jī)名的請求共享一個 Socket:參考第4條,同主機(jī)名的請求共享同一條連接,同一條連接及共享同一個 Socket;
