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

