Android OKHttp攔截器和緩存案例詳解
深入理解 OkHttp 攔截器
1. 攔截器接口詳解
Interceptor
接口是自定義攔截器的基礎(chǔ),它僅包含一個(gè)抽象方法 intercept
。以下是對(duì)該方法參數(shù)和返回值的詳細(xì)解釋?zhuān)?/p>
import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class CustomInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { // chain 包含了當(dāng)前請(qǐng)求的所有信息以及后續(xù)攔截器的處理邏輯 Request originalRequest = chain.request(); // 可以對(duì)原始請(qǐng)求進(jìn)行修改,例如添加請(qǐng)求頭、修改請(qǐng)求方法等 Request modifiedRequest = originalRequest.newBuilder() .header("Custom-Header", "Custom-Value") .build(); // proceed 方法會(huì)將修改后的請(qǐng)求傳遞給下一個(gè)攔截器,并返回響應(yīng) Response response = chain.proceed(modifiedRequest); // 可以對(duì)響應(yīng)進(jìn)行處理,例如添加自定義響應(yīng)頭、解析響應(yīng)體等 return response.newBuilder() .header("Custom-Response-Header", "Custom-Response-Value") .build(); } }
Chain
參數(shù):Chain
是一個(gè)接口,它代表了整個(gè)攔截器鏈。chain.request()
方法可以獲取當(dāng)前的請(qǐng)求對(duì)象;
chain.proceed(request)
方法會(huì)將請(qǐng)求傳遞給下一個(gè)攔截器,并返回響應(yīng)。
Response
返回值:intercept
方法必須返回一個(gè) Response
對(duì)象,這個(gè)對(duì)象可以是原始響應(yīng),也可以是經(jīng)過(guò)修改后的響應(yīng)。
2.攔截器鏈的詳細(xì)執(zhí)行流程
整體流程
OkHttp 的攔截器鏈?zhǔn)且粋€(gè)有序的攔截器集合,請(qǐng)求和響應(yīng)會(huì)依次經(jīng)過(guò)每個(gè)攔截器。攔截器鏈的執(zhí)行順序是固定的,如下所示:
1. 用戶自定義攔截器(client.interceptors())
- 位置:攔截器鏈的最前端。
- 作用:這是開(kāi)發(fā)者可以自定義添加的攔截器,開(kāi)發(fā)者可以在這個(gè)攔截器中實(shí)現(xiàn)一些通用的業(yè)務(wù)邏輯,比如統(tǒng)一添加請(qǐng)求頭、日志記錄、請(qǐng)求參數(shù)加密等操作。由于它處于攔截器鏈的最前端,所以可以對(duì)原始的請(qǐng)求進(jìn)行最早的處理,并且能獲取到最終的響應(yīng),方便進(jìn)行日志記錄等操作。
- 示例代碼:
import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class CustomHeaderInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request originalRequest = chain.request(); Request newRequest = originalRequest.newBuilder() .header("Custom-Header", "Custom-Value") .build(); return chain.proceed(newRequest); } }
2. 重試和重定向攔截器(RetryAndFollowUpInterceptor)
- 位置:緊跟用戶自定義攔截器之后。
- 作用:負(fù)責(zé)處理請(qǐng)求的重試和重定向邏輯。當(dāng)請(qǐng)求過(guò)程中出現(xiàn)網(wǎng)絡(luò)錯(cuò)誤(如連接超時(shí)、DNS 解析失敗等)時(shí),該攔截器會(huì)根據(jù)配置的重試策略進(jìn)行重試;當(dāng)服務(wù)器返回重定向響應(yīng)(如 301、302 狀態(tài)碼)時(shí),會(huì)自動(dòng)處理重定向請(qǐng)求,重新發(fā)起新的請(qǐng)求到重定向的地址。
- 源碼分析:在
intercept
方法中,會(huì)不斷循環(huán)處理請(qǐng)求,直到請(qǐng)求成功或者達(dá)到最大重試次數(shù)。通過(guò)判斷響應(yīng)的狀態(tài)碼和異常類(lèi)型來(lái)決定是否進(jìn)行重試或重定向操作。
3. 橋接攔截器(BridgeInterceptor)
- 位置:在重試和重定向攔截器之后。
- 作用:主要負(fù)責(zé)將用戶的請(qǐng)求轉(zhuǎn)換為符合網(wǎng)絡(luò)傳輸規(guī)范的請(qǐng)求。它會(huì)添加一些必要的請(qǐng)求頭,如
Content-Type
、Content-Length
、User-Agent
等,同時(shí)處理請(qǐng)求體的編碼和壓縮。另外,它還會(huì)對(duì)響應(yīng)進(jìn)行一些處理,比如將響應(yīng)頭中的Content-Encoding
信息解析出來(lái),對(duì)響應(yīng)體進(jìn)行相應(yīng)的解碼操作。 - 源碼分析:在
intercept
方法中,會(huì)根據(jù)請(qǐng)求體的情況添加相應(yīng)的請(qǐng)求頭,然后調(diào)用chain.proceed
方法將處理后的請(qǐng)求傳遞給下一個(gè)攔截器,最后對(duì)響應(yīng)進(jìn)行處理并返回。
4. 緩存攔截器(CacheInterceptor)
- 位置:在橋接攔截器之后。
- 作用:負(fù)責(zé)處理請(qǐng)求的緩存邏輯。它會(huì)根據(jù)請(qǐng)求的緩存策略(如
Cache-Control
頭信息)檢查本地緩存中是否存在符合條件的響應(yīng)。如果存在且緩存有效,則直接返回緩存的響應(yīng),避免進(jìn)行網(wǎng)絡(luò)請(qǐng)求;如果緩存無(wú)效或者不存在,則發(fā)起網(wǎng)絡(luò)請(qǐng)求,并將響應(yīng)存入緩存。 - 源碼分析:在
intercept
方法中,會(huì)先從緩存中查找匹配的響應(yīng),然后根據(jù)請(qǐng)求和緩存的情況判斷是否可以使用緩存。如果可以使用緩存,則直接返回緩存響應(yīng);否則,調(diào)用chain.proceed
方法發(fā)起網(wǎng)絡(luò)請(qǐng)求,并將響應(yīng)存入緩存。
5. 連接攔截器(ConnectInterceptor)
- 位置:在緩存攔截器之后。
- 作用:負(fù)責(zé)建立與服務(wù)器的連接。它會(huì)根據(jù)請(qǐng)求的 URL 和配置,選擇合適的連接(如 HTTP/1.1 或 HTTP/2 連接),并進(jìn)行 TCP 握手和 TLS 協(xié)商(如果是 HTTPS 請(qǐng)求)。同時(shí),它會(huì)管理連接池,復(fù)用已經(jīng)建立的連接,減少連接建立的開(kāi)銷(xiāo)。
- 源碼分析:在
intercept
方法中,會(huì)從連接池中獲取可用的連接,如果沒(méi)有可用連接則創(chuàng)建新的連接,然后進(jìn)行連接的建立和握手操作,最后將連接傳遞給下一個(gè)攔截器。
6. 用戶自定義網(wǎng)絡(luò)攔截器(client.networkInterceptors())
- 位置:在連接攔截器之后,僅在進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí)會(huì)執(zhí)行。
- 作用:與用戶自定義攔截器類(lèi)似,但它更側(cè)重于對(duì)網(wǎng)絡(luò)請(qǐng)求和響應(yīng)進(jìn)行處理。由于它在連接建立之后執(zhí)行,所以可以獲取到實(shí)際的網(wǎng)絡(luò)連接信息,并且可以對(duì)網(wǎng)絡(luò)請(qǐng)求和響應(yīng)進(jìn)行更底層的修改,比如修改請(qǐng)求的字節(jié)流、監(jiān)控網(wǎng)絡(luò)流量等。
- 示例代碼:
import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class NetworkLoggingInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); System.out.println(String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); System.out.println(String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; } }
7. 服務(wù)器調(diào)用攔截器(CallServerInterceptor)
- 位置:攔截器鏈的最后一個(gè)攔截器。
- 作用:負(fù)責(zé)向服務(wù)器發(fā)送請(qǐng)求并接收服務(wù)器的響應(yīng)。它會(huì)將請(qǐng)求數(shù)據(jù)寫(xiě)入網(wǎng)絡(luò)連接,然后讀取服務(wù)器返回的響應(yīng)數(shù)據(jù),包括響應(yīng)頭和響應(yīng)體。
- 源碼分析:在
intercept
方法中,會(huì)將請(qǐng)求體寫(xiě)入連接的輸出流,發(fā)送請(qǐng)求頭,然后從連接的輸入流中讀取響應(yīng)頭和響應(yīng)體,最后返回響應(yīng)對(duì)象。
3.應(yīng)用攔截器和網(wǎng)絡(luò)攔截器的區(qū)別
應(yīng)用攔截器
- 添加方式:通過(guò)
OkHttpClient.Builder().addInterceptor(Interceptor interceptor)
方法添加。 - 執(zhí)行時(shí)機(jī):在所有網(wǎng)絡(luò)相關(guān)操作之前執(zhí)行,僅處理應(yīng)用層發(fā)起的原始請(qǐng)求。
- 特點(diǎn): 不會(huì)受到重定向、重試等網(wǎng)絡(luò)操作的影響,每個(gè)請(qǐng)求只會(huì)經(jīng)過(guò)應(yīng)用攔截器一次。可以獲取到最原始的請(qǐng)求和最終的響應(yīng),適合進(jìn)行日志記錄、請(qǐng)求頭添加等操作。
import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class ApplicationInterceptorExample { public static void main(String[] args) throws IOException { CustomInterceptor customInterceptor = new CustomInterceptor(); OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(customInterceptor) .build(); Request request = new Request.Builder() .url("https://example.com") .build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } }
網(wǎng)絡(luò)攔截器
- 添加方式:通過(guò)
OkHttpClient.Builder().addNetworkInterceptor(Interceptor interceptor)
方法添加。 - 執(zhí)行時(shí)機(jī):在建立網(wǎng)絡(luò)連接之后、發(fā)送請(qǐng)求到服務(wù)器之前執(zhí)行,會(huì)處理所有的網(wǎng)絡(luò)請(qǐng)求,包括重定向和重試的請(qǐng)求。
- 特點(diǎn):
- 可以處理網(wǎng)絡(luò)層的細(xì)節(jié),例如請(qǐng)求的重試、重定向等。
- 可能會(huì)執(zhí)行多次,因?yàn)橹囟ㄏ蚝椭卦嚂?huì)導(dǎo)致請(qǐng)求多次經(jīng)過(guò)網(wǎng)絡(luò)攔截器。
import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import java.io.IOException; public class NetworkInterceptorExample { public static void main(String[] args) throws IOException { CustomInterceptor customInterceptor = new CustomInterceptor(); OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(customInterceptor) .build(); Request request = new Request.Builder() .url("https://example.com") .build(); Response response = client.newCall(request).execute(); System.out.println(response.body().string()); } }
4. 攔截器的高級(jí)應(yīng)用場(chǎng)景
緩存控制攔截器
可以創(chuàng)建一個(gè)攔截器來(lái)動(dòng)態(tài)控制緩存策略,例如根據(jù)網(wǎng)絡(luò)狀態(tài)或用戶設(shè)置來(lái)決定是否使用緩存。
import okhttp3.*; import java.io.IOException; public class CacheControlInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (isNetworkAvailable()) { // 網(wǎng)絡(luò)可用時(shí),設(shè)置緩存策略為最多緩存 60 秒 request = request.newBuilder() .header("Cache-Control", "max-age=60") .build(); } else { // 網(wǎng)絡(luò)不可用時(shí),強(qiáng)制使用緩存 request = request.newBuilder() .header("Cache-Control", "only-if-cached") .build(); } return chain.proceed(request); } private boolean isNetworkAvailable() { // 實(shí)現(xiàn)網(wǎng)絡(luò)狀態(tài)檢查邏輯 return true; } }
超時(shí)重試攔截器
可以創(chuàng)建一個(gè)攔截器來(lái)處理請(qǐng)求超時(shí)的情況,當(dāng)請(qǐng)求超時(shí)時(shí),自動(dòng)重試一定次數(shù)。
import okhttp3.*; import java.io.IOException; public class RetryInterceptor implements Interceptor { private static final int MAX_RETRIES = 3; @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response response = null; IOException exception = null; for (int i = 0; i < MAX_RETRIES; i++) { try { response = chain.proceed(request); if (response.isSuccessful()) { break; } } catch (IOException e) { exception = e; } } if (response == null) { throw exception; } return response; } }
擴(kuò)展追問(wèn):
如何保證OKHttp 攔截器鏈中每個(gè)攔截器能按預(yù)定順序執(zhí)行
答:OKHttp 的攔截器順序就像一場(chǎng) “接力賽”:
- 框架規(guī)定了內(nèi)置攔截器的固定跑道(重試→橋接→緩存→連接→網(wǎng)絡(luò)請(qǐng)求);
- 用戶攔截器按類(lèi)型插入特定位置(應(yīng)用攔截器在起點(diǎn),網(wǎng)絡(luò)攔截器在連接之后);
chain.proceed()
是接力棒,確保每個(gè)攔截器按順序處理請(qǐng)求,響應(yīng)按逆序回流,環(huán)環(huán)相扣,不會(huì)混亂。
原理:
攔截器的 intercept
方法調(diào)用
每個(gè)攔截器都實(shí)現(xiàn)了 Interceptor
接口,該接口有一個(gè) intercept
方法。在 intercept
方法中,需要調(diào)用傳入的 Chain
對(duì)象的 proceed
方法,將請(qǐng)求傳遞給下一個(gè)攔截器。例如 BridgeInterceptor
的 intercept
方法:
@Override public Response intercept(Chain chain) throws IOException { Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); // 處理請(qǐng)求頭 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"); } } Request networkRequest = requestBuilder.build(); // 調(diào)用 chain.proceed 方法將請(qǐng)求傳遞給下一個(gè)攔截器 Response networkResponse = chain.proceed(networkRequest); // 處理響應(yīng) Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); return responseBuilder.build(); }
在 intercept
方法中調(diào)用 chain.proceed
方法,就會(huì)觸發(fā)下一個(gè)攔截器的執(zhí)行,進(jìn)而保證攔截器鏈按順序執(zhí)行。
———————————————————————————————————————————
OkHttp 緩存機(jī)制詳解(結(jié)合 Android 面試高頻考點(diǎn))
一、緩存核心組件與配置
Cache
類(lèi)
作用:OkHttp 的緩存通過(guò) Cache
類(lèi)實(shí)現(xiàn),基于磁盤(pán)存儲(chǔ)(默認(rèn)無(wú)內(nèi)存緩存,需手動(dòng)實(shí)現(xiàn))。
初始化:
File cacheDir = new File(context.getCacheDir(), "okhttp_cache"); OkHttpClient client = new OkHttpClient.Builder() .cache(new Cache(cacheDir, 10 * 1024 * 1024)) // 10MB 緩存大小 .build();
面試點(diǎn):緩存目錄通常放在應(yīng)用私有目錄(如 getCacheDir()
),避免權(quán)限問(wèn)題;緩存大小需根據(jù)業(yè)務(wù)場(chǎng)景合理設(shè)置,過(guò)大浪費(fèi)存儲(chǔ),過(guò)小導(dǎo)致緩存命中率低。
CacheInterceptor
攔截器
- 位置:攔截器鏈中的第三個(gè)攔截器(位于
RetryAndFollowUpInterceptor
和ConnectInterceptor
之間)。 - 核心功能:處理緩存的讀取、寫(xiě)入和更新,是緩存機(jī)制的核心邏輯載體。
二、緩存策略(面試高頻考點(diǎn))
OkHttp 支持 HTTP 標(biāo)準(zhǔn)緩存策略(基于 Cache-Control
頭)和 自定義策略,通過(guò) Request
的 CacheControl
對(duì)象配置,常見(jiàn)策略:
強(qiáng)制緩存(不與服務(wù)器交互)
CacheControl.FORCE_CACHE
:優(yōu)先使用緩存,無(wú)緩存時(shí)拋異常(需配合max-stale
等參數(shù))。- 場(chǎng)景:完全離線場(chǎng)景,如兜底頁(yè)面。
緩存優(yōu)先(無(wú)有效緩存時(shí)請(qǐng)求網(wǎng)絡(luò))
CacheControl.cacheControl(CacheControl.Builder() .maxStale(7, TimeUnit.DAYS) // 允許緩存過(guò)期 7 天 .build())
- 流程:先查緩存,若緩存未過(guò)期或允許
max-stale
,直接返回;否則請(qǐng)求網(wǎng)絡(luò),響應(yīng)寫(xiě)入緩存。
網(wǎng)絡(luò)優(yōu)先(忽略緩存,僅存儲(chǔ)響應(yīng))
CacheControl.FORCE_NETWORK
:直接請(qǐng)求網(wǎng)絡(luò),響應(yīng)結(jié)果寫(xiě)入緩存(適用于實(shí)時(shí)數(shù)據(jù))。
協(xié)商緩存(與服務(wù)器驗(yàn)證緩存有效性)
- 利用
ETag
/If-None-Match
或Last-Modified
/If-Modified-Since
頭,服務(wù)器返回304 Not Modified
時(shí)復(fù)用緩存。 - 面試點(diǎn):區(qū)分強(qiáng)制緩存(200 狀態(tài)碼,直接讀緩存)和協(xié)商緩存(304 狀態(tài)碼,需服務(wù)器驗(yàn)證)。
三、緩存存儲(chǔ)結(jié)構(gòu)與 HTTP 頭解析
緩存存儲(chǔ)格式
OkHttp 將響應(yīng)以 二進(jìn)制文件 存儲(chǔ)在磁盤(pán),文件名由 URL 的哈希值生成,包含兩部分:
- 響應(yīng)頭文件(
.headers
):存儲(chǔ)Cache-Control
、ETag
等元信息。 - 響應(yīng)體文件(無(wú)擴(kuò)展名):存儲(chǔ)實(shí)際數(shù)據(jù)。
關(guān)鍵 HTTP 頭字段
Cache-Control
:
max-age
:- 緩存有效期(秒),優(yōu)先級(jí)高于
Expires
。 no-cache
:需走協(xié)商緩存(驗(yàn)證有效性),no-store
:禁止緩存。ETag
/Last-Modified
:協(xié)商緩存的核心字段,OkHttp 自動(dòng)處理If-None-Match
和If-Modified-Since
頭。
四、緩存流程與攔截器邏輯
- 緩存讀?。?code>CacheInterceptor 前半段)
- 從緩存中查找與請(qǐng)求匹配的響應(yīng)(根據(jù) URL、方法、頭信息)。
- 若存在緩存,根據(jù)
Cache-Control
判定是否有效: - 有效:直接返回緩存(跳過(guò)網(wǎng)絡(luò)請(qǐng)求)。
- 過(guò)期但允許
max-stale
:返回緩存,同時(shí)異步更新網(wǎng)絡(luò)數(shù)據(jù)。
網(wǎng)絡(luò)請(qǐng)求與緩存寫(xiě)入(CacheInterceptor
后半段)
- 網(wǎng)絡(luò)響應(yīng)返回后,根據(jù)
Cache-Control
決定是否寫(xiě)入緩存(如max-age > 0
)。 - 寫(xiě)入前檢查響應(yīng)狀態(tài)碼(僅 200 OK 和 304 會(huì)被緩存),并提取必要的頭信息用于后續(xù)驗(yàn)證。
五、內(nèi)存緩存與磁盤(pán)緩存(面試易混點(diǎn))
- 磁盤(pán)緩存:OkHttp 內(nèi)置,通過(guò)
Cache
類(lèi)配置,持久化存儲(chǔ),適合大文件或需離線訪問(wèn)的場(chǎng)景。 - 內(nèi)存緩存:需手動(dòng)實(shí)現(xiàn)(如使用
LruCache
),OkHttp 不默認(rèn)支持,用于加速熱數(shù)據(jù)訪問(wèn),減少磁盤(pán) IO。 - 面試問(wèn)法:“OkHttp 有沒(méi)有內(nèi)存緩存?如何實(shí)現(xiàn)?” 答:默認(rèn)只有磁盤(pán)緩存,內(nèi)存緩存需結(jié)合
Interceptor
手動(dòng)實(shí)現(xiàn),存儲(chǔ)已處理的Response
對(duì)象。
六、緩存失效與更新
手動(dòng)清除緩存
client.cache().delete(); // 清除所有緩存 client.cache().evictAll(); // 同上(API 差異)
策略強(qiáng)制更新
發(fā)起請(qǐng)求時(shí)添加 CacheControl.noCache()
,強(qiáng)制忽略緩存,走網(wǎng)絡(luò)請(qǐng)求。
七、面試高頻問(wèn)題總結(jié)
“OkHttp 緩存策略有哪些?如何實(shí)現(xiàn)緩存優(yōu)先?”
答:支持 FORCE_CACHE
(強(qiáng)制讀緩存)、FORCE_NETWORK
(強(qiáng)制網(wǎng)絡(luò))、協(xié)商緩存(304)等;緩存優(yōu)先可通過(guò) maxStale
允許過(guò)期緩存,配合網(wǎng)絡(luò)請(qǐng)求更新。
“304 狀態(tài)碼在 OkHttp 緩存中如何處理?”
答:OkHttp 自動(dòng)攜帶 ETag
生成 If-None-Match
頭,服務(wù)器返回 304 時(shí),復(fù)用本地緩存響應(yīng)體,僅更新頭信息(減少流量)。
“OkHttp 緩存和瀏覽器緩存的區(qū)別?”
答:核心邏輯一致(基于 HTTP 頭),但 OkHttp 需手動(dòng)配置 Cache
實(shí)例,且默認(rèn)無(wú)內(nèi)存緩存;瀏覽器緩存由瀏覽器自動(dòng)管理。
“緩存攔截器的作用是什么?在攔截器鏈中的位置?”
答:負(fù)責(zé)緩存的讀取和寫(xiě)入,位于攔截器鏈的中間位置(處理完重試、橋接,未處理連接和網(wǎng)絡(luò)請(qǐng)求)。 總結(jié)
OkHttp 緩存機(jī)制通過(guò) 攔截器鏈 和 HTTP 標(biāo)準(zhǔn)頭 實(shí)現(xiàn)高效的網(wǎng)絡(luò)請(qǐng)求優(yōu)化,核心在于合理配置 CacheControl
策略、利用協(xié)商緩存減少服務(wù)器壓力,并結(jié)合磁盤(pán) / 內(nèi)存緩存提升性能。--
_____________________________________________________________________________
OkHttp 的連接池復(fù)用是優(yōu)化網(wǎng)絡(luò)請(qǐng)求性能的重要手段,其核心是通過(guò)ConnectionPool
管理底層 TCP 連接,避免重復(fù)建立連接的開(kāi)銷(xiāo)。
一、OkHttp 連接池復(fù)用的核心原理
目標(biāo)
復(fù)用相同 URL、相同協(xié)議(HTTP/HTTPS)的連接,減少 TCP 三次握手、TLS 握手的耗時(shí),提升請(qǐng)求速度。
核心類(lèi):ConnectionPool
- 作用:維護(hù)一個(gè)連接隊(duì)列,緩存未關(guān)閉的空閑連接,供后續(xù)請(qǐng)求復(fù)用。
- 默認(rèn)配置(
OkHttpClient
默認(rèn)創(chuàng)建):
// OkHttpClient源碼中的默認(rèn)連接池 private static final ConnectionPool DEFAULT_CONNECTION_POOL = new ConnectionPool( 5, // 最大空閑連接數(shù)(默認(rèn)5個(gè)) 5, TimeUnit.MINUTES // 空閑連接存活時(shí)間(默認(rèn)5分鐘) );
關(guān)鍵參數(shù):
maxIdleConnections
:最大空閑連接數(shù),超過(guò)則清理最舊的連接。keepAliveDuration
:空閑連接在池中的最長(zhǎng)存活時(shí)間,超時(shí)則關(guān)閉。
連接復(fù)用條件
- 請(qǐng)求的 URL 的
host
和port
相同,且協(xié)議(HTTP/HTTPS)一致。 - 連接處于 “空閑狀態(tài)”(即當(dāng)前無(wú)請(qǐng)求正在使用,但未超時(shí))。
二、如何使用連接池復(fù)用?
1. 默認(rèn)使用(無(wú)需額外配置)
OkHttpClient 默認(rèn)啟用連接池,無(wú)需手動(dòng)設(shè)置,同一OkHttpClient
實(shí)例的所有請(qǐng)求共享同一個(gè)連接池:
OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .build(); // 內(nèi)部使用默認(rèn)的ConnectionPool
2. 自定義連接池配置(可選)
若需調(diào)整默認(rèn)參數(shù)(如增大空閑連接數(shù)或存活時(shí)間),可通過(guò)connectionPool()
方法設(shè)置:
OkHttpClient client = new OkHttpClient.Builder() .connectionPool(new ConnectionPool( 10, // 最大空閑連接數(shù)設(shè)為10 10, TimeUnit.MINUTES // 空閑連接存活時(shí)間設(shè)為10分鐘 )) .build();
3. 連接池的生命周期
- 自動(dòng)清理:OkHttp 通過(guò)后臺(tái)線程(
CleanupRunnable
)定時(shí)檢查(每隔 5 秒),清理超時(shí)的空閑連接。 - 手動(dòng)清理(罕見(jiàn)場(chǎng)景):如需立即釋放資源(如應(yīng)用退出時(shí)),可調(diào)用:
client.connectionPool().evictAll(); // 清除所有連接
三、源碼級(jí)實(shí)現(xiàn)細(xì)節(jié)(面試常問(wèn))
連接獲取流程
當(dāng)發(fā)起請(qǐng)求時(shí),OkHttp 先從ConnectionPool
中查找可用的空閑連接:
// RealConnectionPool.java 查找連接的核心邏輯 RealConnection get(Address address, StreamAllocation streamAllocation) { // 遍歷連接池中的連接,尋找匹配address且空閑的連接 for (RealConnection connection : connections) { if (connection.isEligible(address, streamAllocation)) { streamAllocation.acquire(connection); return connection; } } return null; // 無(wú)可用連接,新建連接 }
isEligible
方法判斷連接是否符合復(fù)用條件(host、port、協(xié)議一致,且未達(dá)最大請(qǐng)求數(shù))。
連接釋放與空閑標(biāo)記
請(qǐng)求完成后,連接不會(huì)立即關(guān)閉,而是標(biāo)記為 “空閑” 并放回連接池:
// RealConnection.java 釋放連接的邏輯 void release(StreamAllocation streamAllocation) { if (streamAllocation == null) return; streamAllocation.release(); if (allocationCount > 0 || noNewStreams) { return; // 連接仍在使用中 } // 連接變?yōu)榭臻e,加入連接池的空閑隊(duì)列 connectionPool.put(this); }
清理機(jī)制
ConnectionPool
通過(guò)CleanupRunnable
線程定時(shí)執(zhí)行cleanup()
方法,移除超時(shí)或超出最大空閑數(shù)的連接:
// ConnectionPool.java 清理邏輯 private final Runnable cleanupRunnable = () -> { while (true) { long waitNanos = cleanup(System.nanoTime()); // 執(zhí)行清理,返回下次等待時(shí)間 if (waitNanos == -1) return; // 無(wú)需要清理的連接,退出 if (waitNanos > 0) { synchronized (this) { try { wait(waitNanos / 1000000, (int) (waitNanos % 1000000)); } catch (InterruptedException e) { return; } } } } };
四、面試高頻問(wèn)題與解答
1. 為什么需要連接池復(fù)用?相比 HTTPURLConnection 有什么優(yōu)勢(shì)?
原因:避免重復(fù)建立 TCP 連接(三次握手)和 TLS 握手(HTTPS 場(chǎng)景),減少延遲和資源消耗。
優(yōu)勢(shì):
- HTTPURLConnection 默認(rèn)不支持連接復(fù)用(需手動(dòng)配置
HttpURLConnection.setInstanceFollowRedirects(true)
,且管理復(fù)雜); - OkHttp 的
ConnectionPool
自動(dòng)管理連接生命周期,線程安全,開(kāi)箱即用。
2. 如何判斷兩個(gè)請(qǐng)求是否可以復(fù)用同一個(gè)連接?
必須滿足:
- URL 的
host
和port
相同; - 協(xié)議相同(均為 HTTP 或均為 HTTPS);
- 連接處于空閑狀態(tài)(未被占用且未超時(shí))。
3. 連接池中的連接會(huì)一直存在嗎?如何避免內(nèi)存泄漏?
不會(huì):
- 超過(guò)
maxIdleConnections
的空閑連接會(huì)被清理; - 超過(guò)
keepAliveDuration
的空閑連接會(huì)被關(guān)閉; - 應(yīng)用退出時(shí),建議調(diào)用
connectionPool.evictAll()
釋放所有連接。
最佳實(shí)踐:使用單例OkHttpClient
(避免創(chuàng)建多個(gè)實(shí)例導(dǎo)致多個(gè)連接池),并合理設(shè)置maxIdleConnections
(通常默認(rèn)值即可)。
4. 連接池和緩存機(jī)制(CacheInterceptor)的關(guān)系是什么?
- 連接池優(yōu)化的是 “網(wǎng)絡(luò)連接層” 的性能(減少連接建立開(kāi)銷(xiāo));
- 緩存機(jī)制優(yōu)化的是 “應(yīng)用層” 的性能(直接返回本地緩存,避免網(wǎng)絡(luò)請(qǐng)求);
- 兩者可同時(shí)使用,共同提升性能。
五、最佳實(shí)踐
單例模式:全局共享一個(gè)OkHttpClient
實(shí)例,避免重復(fù)創(chuàng)建連接池:
public class OkHttpSingleton { private static OkHttpClient client; public static OkHttpClient getInstance() { if (client == null) { synchronized (OkHttpSingleton.class) { if (client == null) { client = new OkHttpClient.Builder() .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES)) .build(); } } } return client; } }
- 結(jié)合 HTTPS 優(yōu)化:復(fù)用連接時(shí),TLS 握手僅在首次建立連接時(shí)執(zhí)行,后續(xù)請(qǐng)求直接復(fù)用已建立的加密通道。
- 監(jiān)控與調(diào)試:通過(guò)
EventListener
監(jiān)聽(tīng)連接池事件(如連接創(chuàng)建、復(fù)用、釋放),排查性能問(wèn)題。
總結(jié)
OkHttp 的連接池復(fù)用通過(guò)ConnectionPool
自動(dòng)管理空閑連接,顯著提升網(wǎng)絡(luò)請(qǐng)求效率。使用時(shí)無(wú)需手動(dòng)干預(yù),只需合理配置參數(shù)(或使用默認(rèn)值),并遵循單例模式共享OkHttpClient
實(shí)例即可。
擴(kuò)展追問(wèn):
1. OkHttp 如何實(shí)現(xiàn)連接池復(fù)用?(高頻題)
核心回答點(diǎn):
- ConnectionPool 組件:OkHttp 通過(guò)
ConnectionPool
管理連接,默認(rèn)維護(hù) 5 個(gè)空閑連接(maxIdleConnections
),存活時(shí)間 5 分鐘(keepAliveDuration
)。 - 復(fù)用邏輯:新請(qǐng)求優(yōu)先從連接池中查找匹配的空閑連接(同主機(jī)、端口、協(xié)議),避免重復(fù)創(chuàng)建 TCP 連接和 TLS 握手,減少延遲。
- 連接回收:請(qǐng)求完成后,連接不會(huì)立即關(guān)閉,而是放入池中等待復(fù)用;若空閑時(shí)間超過(guò)閾值或連接數(shù)超過(guò)上限,通過(guò)后臺(tái)線程(
cleanupRunnable
)定期清理過(guò)期連接。 - 面試加分項(xiàng):對(duì)比 HTTP/1.1 的
Connection: Keep-Alive
,OkHttp 實(shí)現(xiàn)更高效,支持自動(dòng)管理連接生命周期,降低資源消耗。
2. OkHttp 緩存機(jī)制的核心策略是什么?如何配置?(必問(wèn)題)
核心回答點(diǎn):
兩層緩存:
- 內(nèi)存緩存(
CacheInterceptor
管理):存儲(chǔ)響應(yīng)數(shù)據(jù),快速響應(yīng)重復(fù)請(qǐng)求,減少 CPU 和內(nèi)存開(kāi)銷(xiāo)。 - 磁盤(pán)緩存(
Cache
類(lèi),需手動(dòng)創(chuàng)建):持久化存儲(chǔ),應(yīng)對(duì) APP 重啟或長(zhǎng)時(shí)間未請(qǐng)求的場(chǎng)景。 - 緩存策略:通過(guò)
CacheControl
頭配置,如FORCE_CACHE
(優(yōu)先讀緩存)、FORCE_NETWORK
(強(qiáng)制走網(wǎng)絡(luò))、CACHE_ELSE_NETWORK
(緩存失效后走網(wǎng)絡(luò))。 - 面試陷阱:需區(qū)分 強(qiáng)緩存(
304 Not Modified
)和 協(xié)商緩存(服務(wù)端驗(yàn)證緩存有效性),OkHttp 內(nèi)置攔截器自動(dòng)處理緩存響應(yīng)碼。 - 配置示例:創(chuàng)建
Cache
對(duì)象并設(shè)置大?。ㄈ?nbsp;new Cache(cacheDir, 10 * 1024 * 1024)
),通過(guò)OkHttpClient.Builder().cache(cache)
綁定。
3. 攔截器鏈(Interceptor Chain)的作用是什么?自定義攔截器如何實(shí)現(xiàn)?(原理題)
核心回答點(diǎn):
- 責(zé)任鏈模式:OkHttp 通過(guò)攔截器鏈處理請(qǐng)求和響應(yīng),包括 重試與重定向、橋接(添加請(qǐng)求頭 / 處理響應(yīng)體)、緩存、連接建立、網(wǎng)絡(luò)請(qǐng)求 等內(nèi)置攔截器。
- 執(zhí)行順序:用戶自定義攔截器 → 內(nèi)置重試攔截器 → 橋接攔截器 → 緩存攔截器 → 連接攔截器 → 網(wǎng)絡(luò)攔截器 → 調(diào)用服務(wù)器攔截器。
- 自定義場(chǎng)景:用于添加公共請(qǐng)求頭(如 Token)、日志打印、響應(yīng)數(shù)據(jù)解析 / 修改,通過(guò)實(shí)現(xiàn)
Interceptor
接口的intercept
方法,調(diào)用chain.proceed(request)
傳遞請(qǐng)求。 - 面試關(guān)鍵:強(qiáng)調(diào)攔截器的 “中間件” 特性,可在不修改核心代碼的前提下擴(kuò)展功能,符合開(kāi)閉原則。
4. 同步請(qǐng)求(execute)和異步請(qǐng)求(enqueue)的區(qū)別是什么?如何實(shí)現(xiàn)線程切換?(線程題)
核心回答點(diǎn):
- 執(zhí)行方式: 同步:阻塞當(dāng)前線程,在主線程調(diào)用會(huì)導(dǎo)致 ANR,需在子線程執(zhí)行,直接返回
Response
。 - 異步:通過(guò)
Dispatcher
調(diào)度到線程池(默認(rèn)ExecutorService
),回調(diào)Callback
在子線程,需手動(dòng)通過(guò)Handler
切回主線程。 - 線程管理:
Dispatcher
控制最大并發(fā)請(qǐng)求數(shù)(默認(rèn) 64 個(gè),同一主機(jī) 5 個(gè)),異步請(qǐng)求通過(guò)AsyncCall
封裝,放入隊(duì)列或直接執(zhí)行。 - 面試陷阱:避免混淆 “異步回調(diào)是否在主線程”,OkHttp 不負(fù)責(zé)線程切換,需開(kāi)發(fā)者自行處理(如
runOnUiThread
)。
5. OkHttp 相比 Volley 或 HttpURLConnection 的優(yōu)勢(shì)是什么?(對(duì)比題)
核心回答點(diǎn):
- 性能優(yōu)化:連接池復(fù)用、緩存策略、SPDY/HTTP/2 支持(減少 TCP 連接數(shù)),網(wǎng)絡(luò)請(qǐng)求效率更高。
- 擴(kuò)展性:攔截器機(jī)制靈活,方便添加日志、重試、加密等功能,而 Volley 更適合小量短連接請(qǐng)求。
- 穩(wěn)定性:內(nèi)置重試機(jī)制(自動(dòng)處理連接超時(shí)、重定向),支持流式響應(yīng)處理(大文件下載),適合復(fù)雜網(wǎng)絡(luò)場(chǎng)景。
- 面試加分:結(jié)合實(shí)際項(xiàng)目,說(shuō)明 OkHttp 在處理高并發(fā)、大文件、復(fù)雜網(wǎng)絡(luò)環(huán)境下的優(yōu)勢(shì)。
6. 如何優(yōu)化 OkHttp 的網(wǎng)絡(luò)請(qǐng)求性能?(實(shí)戰(zhàn)題)
核心回答點(diǎn):
- 連接池調(diào)優(yōu):根據(jù)業(yè)務(wù)場(chǎng)景調(diào)整
maxIdleConnections
和keepAliveDuration
(如高頻接口增大連接數(shù))。 - 緩存策略:合理設(shè)置緩存有效期(
CacheControl.maxAge
),減少無(wú)效網(wǎng)絡(luò)請(qǐng)求。 - HTTPS 優(yōu)化:使用
CertificatePinner
固定證書(shū),避免 SSL 握手耗時(shí);啟用 HTTP/2(需服務(wù)端支持)。 - 并發(fā)控制:通過(guò)
Dispatcher.setMaxRequests
和setMaxRequestsPerHost
限制并發(fā),避免資源耗盡。
到此這篇關(guān)于Android學(xué)習(xí)總結(jié)之OKHttp攔截器和緩存的文章就介紹到這了,更多相關(guān)Android OKHttp攔截器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android畢業(yè)設(shè)計(jì)記事本APP
這篇文章主要介紹了一個(gè)Android畢業(yè)設(shè)計(jì)記事本APP,它是一款輕量級(jí)的便簽工具,使用Java語(yǔ)言開(kāi)發(fā),風(fēng)格簡(jiǎn)練,可實(shí)現(xiàn)便簽的添加、刪除、修改、查看功能2021-08-08Android自定義View仿大眾點(diǎn)評(píng)星星評(píng)分控件
這篇文章主要為大家詳細(xì)介紹了Android自定義View仿大眾點(diǎn)評(píng)星星評(píng)分控件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03Android 文件夾顯示紅色嘆號(hào)的解決方法(必看)
下面小編就為大家?guī)?lái)一篇Android 文件夾顯示紅色嘆號(hào)的解決方法(必看)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04Android實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼功能
這篇文章主要介紹了Android實(shí)現(xiàn)滑塊拼圖驗(yàn)證碼功能,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Android編程處理窗口控件大小,形狀,像素等UI元素工具類(lèi)
這篇文章主要介紹了Android編程處理窗口控件大小,形狀,像素等UI元素工具類(lèi),可實(shí)現(xiàn)像素與dp的轉(zhuǎn)換、窗口寬度設(shè)置、彈出窗口中l(wèi)istview高度設(shè)置等功能,需要的朋友可以參考下2017-12-12