深入理解 OkHttp 拦截器
1. 拦截器接口详解
Interceptor
接口是自定义拦截器的基础,它仅包含一个抽象方法 intercept
。以下是对该方法参数和返回值的详细解释:
java
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 包含了当前请求的所有信息以及后续拦截器的处理逻辑
Request originalRequest = chain.request();
// 可以对原始请求进行修改,例如添加请求头、修改请求方法等
Request modifiedRequest = originalRequest.newBuilder()
.header("Custom-Header", "Custom-Value")
.build();
// proceed 方法会将修改后的请求传递给下一个拦截器,并返回响应
Response response = chain.proceed(modifiedRequest);
// 可以对响应进行处理,例如添加自定义响应头、解析响应体等
return response.newBuilder()
.header("Custom-Response-Header", "Custom-Response-Value")
.build();
}
}
Chain
参数 :Chain
是一个接口,它代表了整个拦截器链。chain.request()
方法可以获取当前的请求对象;chain.proceed(request)
方法会将请求传递给下一个拦截器,并返回响应。Response
返回值 :intercept
方法必须返回一个Response
对象,这个对象可以是原始响应,也可以是经过修改后的响应。
2.拦截器链的详细执行流程
整体流程
OkHttp 的拦截器链是一个有序的拦截器集合,请求和响应会依次经过每个拦截器。拦截器链的执行顺序是固定的,如下所示:
1. 用户自定义拦截器(client.interceptors()
)
- 位置:拦截器链的最前端。
- 作用:这是开发者可以自定义添加的拦截器,开发者可以在这个拦截器中实现一些通用的业务逻辑,比如统一添加请求头、日志记录、请求参数加密等操作。由于它处于拦截器链的最前端,所以可以对原始的请求进行最早的处理,并且能获取到最终的响应,方便进行日志记录等操作。
- 示例代码:
java
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
)
- 位置:紧跟用户自定义拦截器之后。
- 作用:负责处理请求的重试和重定向逻辑。当请求过程中出现网络错误(如连接超时、DNS 解析失败等)时,该拦截器会根据配置的重试策略进行重试;当服务器返回重定向响应(如 301、302 状态码)时,会自动处理重定向请求,重新发起新的请求到重定向的地址。
- 源码分析 :在
intercept
方法中,会不断循环处理请求,直到请求成功或者达到最大重试次数。通过判断响应的状态码和异常类型来决定是否进行重试或重定向操作。
3. 桥接拦截器(BridgeInterceptor
)
- 位置:在重试和重定向拦截器之后。
- 作用 :主要负责将用户的请求转换为符合网络传输规范的请求。它会添加一些必要的请求头,如
Content-Type
、Content-Length
、User-Agent
等,同时处理请求体的编码和压缩。另外,它还会对响应进行一些处理,比如将响应头中的Content-Encoding
信息解析出来,对响应体进行相应的解码操作。 - 源码分析 :在
intercept
方法中,会根据请求体的情况添加相应的请求头,然后调用chain.proceed
方法将处理后的请求传递给下一个拦截器,最后对响应进行处理并返回。
4. 缓存拦截器(CacheInterceptor
)
- 位置:在桥接拦截器之后。
- 作用 :负责处理请求的缓存逻辑。它会根据请求的缓存策略(如
Cache-Control
头信息)检查本地缓存中是否存在符合条件的响应。如果存在且缓存有效,则直接返回缓存的响应,避免进行网络请求;如果缓存无效或者不存在,则发起网络请求,并将响应存入缓存。 - 源码分析 :在
intercept
方法中,会先从缓存中查找匹配的响应,然后根据请求和缓存的情况判断是否可以使用缓存。如果可以使用缓存,则直接返回缓存响应;否则,调用chain.proceed
方法发起网络请求,并将响应存入缓存。
5. 连接拦截器(ConnectInterceptor
)
- 位置:在缓存拦截器之后。
- 作用:负责建立与服务器的连接。它会根据请求的 URL 和配置,选择合适的连接(如 HTTP/1.1 或 HTTP/2 连接),并进行 TCP 握手和 TLS 协商(如果是 HTTPS 请求)。同时,它会管理连接池,复用已经建立的连接,减少连接建立的开销。
- 源码分析 :在
intercept
方法中,会从连接池中获取可用的连接,如果没有可用连接则创建新的连接,然后进行连接的建立和握手操作,最后将连接传递给下一个拦截器。
6. 用户自定义网络拦截器(client.networkInterceptors()
)
- 位置:在连接拦截器之后,仅在进行网络请求时会执行。
- 作用:与用户自定义拦截器类似,但它更侧重于对网络请求和响应进行处理。由于它在连接建立之后执行,所以可以获取到实际的网络连接信息,并且可以对网络请求和响应进行更底层的修改,比如修改请求的字节流、监控网络流量等。
- 示例代码:
java
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. 服务器调用拦截器(CallServerInterceptor
)
- 位置:拦截器链的最后一个拦截器。
- 作用:负责向服务器发送请求并接收服务器的响应。它会将请求数据写入网络连接,然后读取服务器返回的响应数据,包括响应头和响应体。
- 源码分析 :在
intercept
方法中,会将请求体写入连接的输出流,发送请求头,然后从连接的输入流中读取响应头和响应体,最后返回响应对象。
3.应用拦截器和网络拦截器的区别
应用拦截器
- 添加方式 :通过
OkHttpClient.Builder().addInterceptor(Interceptor interceptor)
方法添加。 - 执行时机:在所有网络相关操作之前执行,仅处理应用层发起的原始请求。
- 特点 :
- 不会受到重定向、重试等网络操作的影响,每个请求只会经过应用拦截器一次。
- 可以获取到最原始的请求和最终的响应,适合进行日志记录、请求头添加等操作。
java
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());
}
}
网络拦截器
- 添加方式 :通过
OkHttpClient.Builder().addNetworkInterceptor(Interceptor interceptor)
方法添加。 - 执行时机:在建立网络连接之后、发送请求到服务器之前执行,会处理所有的网络请求,包括重定向和重试的请求。
- 特点 :
- 可以处理网络层的细节,例如请求的重试、重定向等。
- 可能会执行多次,因为重定向和重试会导致请求多次经过网络拦截器。
java
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. 拦截器的高级应用场景
缓存控制拦截器
可以创建一个拦截器来动态控制缓存策略,例如根据网络状态或用户设置来决定是否使用缓存。
java
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()) {
// 网络可用时,设置缓存策略为最多缓存 60 秒
request = request.newBuilder()
.header("Cache-Control", "max-age=60")
.build();
} else {
// 网络不可用时,强制使用缓存
request = request.newBuilder()
.header("Cache-Control", "only-if-cached")
.build();
}
return chain.proceed(request);
}
private boolean isNetworkAvailable() {
// 实现网络状态检查逻辑
return true;
}
}
超时重试拦截器
可以创建一个拦截器来处理请求超时的情况,当请求超时时,自动重试一定次数。
java
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 拦截器链中每个拦截器能按预定顺序执行
答:OKHttp 的拦截器顺序就像一场 "接力赛":
- 框架规定了内置拦截器的固定跑道(重试→桥接→缓存→连接→网络请求);
- 用户拦截器按类型插入特定位置(应用拦截器在起点,网络拦截器在连接之后);
chain.proceed()
是接力棒,确保每个拦截器按顺序处理请求,响应按逆序回流,环环相扣,不会混乱。
原理:
拦截器的 intercept
方法调用
每个拦截器都实现了 Interceptor
接口,该接口有一个 intercept
方法。在 intercept
方法中,需要调用传入的 Chain
对象的 proceed
方法,将请求传递给下一个拦截器。例如 BridgeInterceptor
的 intercept
方法:
java
@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();
// 调用 chain.proceed 方法将请求传递给下一个拦截器
Response networkResponse = chain.proceed(networkRequest);
// 处理响应
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
return responseBuilder.build();
}
在 intercept
方法中调用 chain.proceed
方法,就会触发下一个拦截器的执行,进而保证拦截器链按顺序执行。
OkHttp 缓存机制详解(结合 Android 面试高频考点)
一、缓存核心组件与配置
-
Cache
类-
作用 :OkHttp 的缓存通过
Cache
类实现,基于磁盘存储(默认无内存缓存,需手动实现)。 -
初始化 :
javaFile cacheDir = new File(context.getCacheDir(), "okhttp_cache"); OkHttpClient client = new OkHttpClient.Builder() .cache(new Cache(cacheDir, 10 * 1024 * 1024)) // 10MB 缓存大小 .build();
-
面试点 :缓存目录通常放在应用私有目录(如
getCacheDir()
),避免权限问题;缓存大小需根据业务场景合理设置,过大浪费存储,过小导致缓存命中率低。
-
-
CacheInterceptor
拦截器- 位置 :拦截器链中的第三个拦截器(位于
RetryAndFollowUpInterceptor
和ConnectInterceptor
之间)。 - 核心功能:处理缓存的读取、写入和更新,是缓存机制的核心逻辑载体。
- 位置 :拦截器链中的第三个拦截器(位于
二、缓存策略(面试高频考点)
OkHttp 支持 HTTP 标准缓存策略 (基于 Cache-Control
头)和 自定义策略 ,通过 Request
的 CacheControl
对象配置,常见策略:
-
强制缓存(不与服务器交互)
CacheControl.FORCE_CACHE
:优先使用缓存,无缓存时抛异常(需配合max-stale
等参数)。- 场景:完全离线场景,如兜底页面。
-
缓存优先(无有效缓存时请求网络)
CacheControl.cacheControl(CacheControl.Builder() .maxStale(7, TimeUnit.DAYS) // 允许缓存过期 7 天 .build())
- 流程 :先查缓存,若缓存未过期或允许
max-stale
,直接返回;否则请求网络,响应写入缓存。
-
网络优先(忽略缓存,仅存储响应)
CacheControl.FORCE_NETWORK
:直接请求网络,响应结果写入缓存(适用于实时数据)。
-
协商缓存(与服务器验证缓存有效性)
- 利用
ETag
/If-None-Match
或Last-Modified
/If-Modified-Since
头,服务器返回304 Not Modified
时复用缓存。 - 面试点:区分强制缓存(200 状态码,直接读缓存)和协商缓存(304 状态码,需服务器验证)。
- 利用
三、缓存存储结构与 HTTP 头解析
-
缓存存储格式
- OkHttp 将响应以 二进制文件 存储在磁盘,文件名由 URL 的哈希值生成,包含两部分:
- 响应头文件 (
.headers
):存储Cache-Control
、ETag
等元信息。 - 响应体文件(无扩展名):存储实际数据。
- 响应头文件 (
- OkHttp 将响应以 二进制文件 存储在磁盘,文件名由 URL 的哈希值生成,包含两部分:
-
关键 HTTP 头字段
Cache-Control
:max-age
:缓存有效期(秒),优先级高于Expires
。no-cache
:需走协商缓存(验证有效性),no-store
:禁止缓存。
ETag
/Last-Modified
:协商缓存的核心字段,OkHttp 自动处理If-None-Match
和If-Modified-Since
头。
四、缓存流程与拦截器逻辑
-
缓存读取(
CacheInterceptor
前半段)- 从缓存中查找与请求匹配的响应(根据 URL、方法、头信息)。
- 若存在缓存,根据
Cache-Control
判定是否有效:- 有效:直接返回缓存(跳过网络请求)。
- 过期但允许
max-stale
:返回缓存,同时异步更新网络数据。
-
网络请求与缓存写入(
CacheInterceptor
后半段)- 网络响应返回后,根据
Cache-Control
决定是否写入缓存(如max-age > 0
)。 - 写入前检查响应状态码(仅 200 OK 和 304 会被缓存),并提取必要的头信息用于后续验证。
- 网络响应返回后,根据
五、内存缓存与磁盘缓存(面试易混点)
- 磁盘缓存 :OkHttp 内置,通过
Cache
类配置,持久化存储,适合大文件或需离线访问的场景。 - 内存缓存 :需手动实现(如使用
LruCache
),OkHttp 不默认支持,用于加速热数据访问,减少磁盘 IO。 - 面试问法 :"OkHttp 有没有内存缓存?如何实现?" 答:默认只有磁盘缓存,内存缓存需结合
Interceptor
手动实现,存储已处理的Response
对象。
六、缓存失效与更新
-
手动清除缓存
javaclient.cache().delete(); // 清除所有缓存 client.cache().evictAll(); // 同上(API 差异)
-
策略强制更新
- 发起请求时添加
CacheControl.noCache()
,强制忽略缓存,走网络请求。
- 发起请求时添加
七、面试高频问题总结
-
"OkHttp 缓存策略有哪些?如何实现缓存优先?"
- 答:支持
FORCE_CACHE
(强制读缓存)、FORCE_NETWORK
(强制网络)、协商缓存(304)等;缓存优先可通过maxStale
允许过期缓存,配合网络请求更新。
- 答:支持
-
"304 状态码在 OkHttp 缓存中如何处理?"
- 答:OkHttp 自动携带
ETag
生成If-None-Match
头,服务器返回 304 时,复用本地缓存响应体,仅更新头信息(减少流量)。
- 答:OkHttp 自动携带
-
"OkHttp 缓存和浏览器缓存的区别?"
- 答:核心逻辑一致(基于 HTTP 头),但 OkHttp 需手动配置
Cache
实例,且默认无内存缓存;浏览器缓存由浏览器自动管理。
- 答:核心逻辑一致(基于 HTTP 头),但 OkHttp 需手动配置
-
"缓存拦截器的作用是什么?在拦截器链中的位置?"
- 答:负责缓存的读取和写入,位于拦截器链的中间位置(处理完重试、桥接,未处理连接和网络请求)。
总结
OkHttp 缓存机制通过 拦截器链 和 HTTP 标准头 实现高效的网络请求优化,核心在于合理配置 CacheControl
策略、利用协商缓存减少服务器压力,并结合磁盘 / 内存缓存提升性能。--
OkHttp 的连接池复用是优化网络请求性能的重要手段,其核心是通过ConnectionPool
管理底层 TCP 连接,避免重复建立连接的开销。
一、OkHttp 连接池复用的核心原理
-
目标
复用相同 URL、相同协议(HTTP/HTTPS)的连接,减少 TCP 三次握手、TLS 握手的耗时,提升请求速度。
-
核心类:ConnectionPool
-
作用:维护一个连接队列,缓存未关闭的空闲连接,供后续请求复用。
-
默认配置 (
OkHttpClient
默认创建):java// OkHttpClient源码中的默认连接池 private static final ConnectionPool DEFAULT_CONNECTION_POOL = new ConnectionPool( 5, // 最大空闲连接数(默认5个) 5, TimeUnit.MINUTES // 空闲连接存活时间(默认5分钟) );
-
关键参数 :
maxIdleConnections
:最大空闲连接数,超过则清理最旧的连接。keepAliveDuration
:空闲连接在池中的最长存活时间,超时则关闭。
-
-
连接复用条件
- 请求的 URL 的
host
和port
相同,且协议(HTTP/HTTPS)一致。 - 连接处于 "空闲状态"(即当前无请求正在使用,但未超时)。
- 请求的 URL 的
二、如何使用连接池复用?
1. 默认使用(无需额外配置)
OkHttpClient 默认启用连接池,无需手动设置,同一OkHttpClient
实例的所有请求共享同一个连接池:
java
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.build(); // 内部使用默认的ConnectionPool
2. 自定义连接池配置(可选)
若需调整默认参数(如增大空闲连接数或存活时间),可通过connectionPool()
方法设置:
java
OkHttpClient client = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(
10, // 最大空闲连接数设为10
10, TimeUnit.MINUTES // 空闲连接存活时间设为10分钟
))
.build();
3. 连接池的生命周期
-
自动清理 :OkHttp 通过后台线程(
CleanupRunnable
)定时检查(每隔 5 秒),清理超时的空闲连接。 -
手动清理 (罕见场景):如需立即释放资源(如应用退出时),可调用:
javaclient.connectionPool().evictAll(); // 清除所有连接
三、源码级实现细节(面试常问)
-
连接获取流程
-
当发起请求时,OkHttp 先从
ConnectionPool
中查找可用的空闲连接:java// 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
方法判断连接是否符合复用条件(host、port、协议一致,且未达最大请求数)。
-
-
连接释放与空闲标记
-
请求完成后,连接不会立即关闭,而是标记为 "空闲" 并放回连接池:
java// RealConnection.java 释放连接的逻辑 void release(StreamAllocation streamAllocation) { if (streamAllocation == null) return; streamAllocation.release(); if (allocationCount > 0 || noNewStreams) { return; // 连接仍在使用中 } // 连接变为空闲,加入连接池的空闲队列 connectionPool.put(this); }
-
-
清理机制
-
ConnectionPool
通过CleanupRunnable
线程定时执行cleanup()
方法,移除超时或超出最大空闲数的连接:java// ConnectionPool.java 清理逻辑 private final Runnable cleanupRunnable = () -> { while (true) { long waitNanos = cleanup(System.nanoTime()); // 执行清理,返回下次等待时间 if (waitNanos == -1) return; // 无需要清理的连接,退出 if (waitNanos > 0) { synchronized (this) { try { wait(waitNanos / 1000000, (int) (waitNanos % 1000000)); } catch (InterruptedException e) { return; } } } } };
-
四、面试高频问题与解答
1. 为什么需要连接池复用?相比 HTTPURLConnection 有什么优势?
- 原因:避免重复建立 TCP 连接(三次握手)和 TLS 握手(HTTPS 场景),减少延迟和资源消耗。
- 优势 :
- HTTPURLConnection 默认不支持连接复用(需手动配置
HttpURLConnection.setInstanceFollowRedirects(true)
,且管理复杂); - OkHttp 的
ConnectionPool
自动管理连接生命周期,线程安全,开箱即用。
- HTTPURLConnection 默认不支持连接复用(需手动配置
2. 如何判断两个请求是否可以复用同一个连接?
- 必须满足:
- URL 的
host
和port
相同; - 协议相同(均为 HTTP 或均为 HTTPS);
- 连接处于空闲状态(未被占用且未超时)。
- URL 的
3. 连接池中的连接会一直存在吗?如何避免内存泄漏?
- 不会 :
- 超过
maxIdleConnections
的空闲连接会被清理; - 超过
keepAliveDuration
的空闲连接会被关闭; - 应用退出时,建议调用
connectionPool.evictAll()
释放所有连接。
- 超过
- 最佳实践 :使用单例
OkHttpClient
(避免创建多个实例导致多个连接池),并合理设置maxIdleConnections
(通常默认值即可)。
4. 连接池和缓存机制(CacheInterceptor)的关系是什么?
- 连接池优化的是 "网络连接层" 的性能(减少连接建立开销);
- 缓存机制优化的是 "应用层" 的性能(直接返回本地缓存,避免网络请求);
- 两者可同时使用,共同提升性能。
五、最佳实践
-
单例模式 :全局共享一个
OkHttpClient
实例,避免重复创建连接池:javapublic 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; } }
-
结合 HTTPS 优化:复用连接时,TLS 握手仅在首次建立连接时执行,后续请求直接复用已建立的加密通道。
-
监控与调试 :通过
EventListener
监听连接池事件(如连接创建、复用、释放),排查性能问题。
总结
OkHttp 的连接池复用通过ConnectionPool
自动管理空闲连接,显著提升网络请求效率。使用时无需手动干预,只需合理配置参数(或使用默认值),并遵循单例模式共享OkHttpClient
实例即可。
扩展追问:
1. OkHttp 如何实现连接池复用?(高频题)
核心回答点:
- ConnectionPool 组件 :OkHttp 通过
ConnectionPool
管理连接,默认维护 5 个空闲连接(maxIdleConnections
),存活时间 5 分钟(keepAliveDuration
)。 - 复用逻辑:新请求优先从连接池中查找匹配的空闲连接(同主机、端口、协议),避免重复创建 TCP 连接和 TLS 握手,减少延迟。
- 连接回收 :请求完成后,连接不会立即关闭,而是放入池中等待复用;若空闲时间超过阈值或连接数超过上限,通过后台线程(
cleanupRunnable
)定期清理过期连接。 - 面试加分项 :对比 HTTP/1.1 的
Connection: Keep-Alive
,OkHttp 实现更高效,支持自动管理连接生命周期,降低资源消耗。
2. OkHttp 缓存机制的核心策略是什么?如何配置?(必问题)
核心回答点:
- 两层缓存 :
- 内存缓存 (
CacheInterceptor
管理):存储响应数据,快速响应重复请求,减少 CPU 和内存开销。 - 磁盘缓存 (
Cache
类,需手动创建):持久化存储,应对 APP 重启或长时间未请求的场景。
- 内存缓存 (
- 缓存策略 :通过
CacheControl
头配置,如FORCE_CACHE
(优先读缓存)、FORCE_NETWORK
(强制走网络)、CACHE_ELSE_NETWORK
(缓存失效后走网络)。 - 面试陷阱 :需区分 强缓存 (
304 Not Modified
)和 协商缓存(服务端验证缓存有效性),OkHttp 内置拦截器自动处理缓存响应码。 - 配置示例 :创建
Cache
对象并设置大小(如new Cache(cacheDir, 10 * 1024 * 1024)
),通过OkHttpClient.Builder().cache(cache)
绑定。
3. 拦截器链(Interceptor Chain)的作用是什么?自定义拦截器如何实现?(原理题)
核心回答点:
- 责任链模式 :OkHttp 通过拦截器链处理请求和响应,包括 重试与重定向 、桥接(添加请求头 / 处理响应体) 、缓存 、连接建立 、网络请求 等内置拦截器。
- 执行顺序:用户自定义拦截器 → 内置重试拦截器 → 桥接拦截器 → 缓存拦截器 → 连接拦截器 → 网络拦截器 → 调用服务器拦截器。
- 自定义场景 :用于添加公共请求头(如 Token)、日志打印、响应数据解析 / 修改,通过实现
Interceptor
接口的intercept
方法,调用chain.proceed(request)
传递请求。 - 面试关键:强调拦截器的 "中间件" 特性,可在不修改核心代码的前提下扩展功能,符合开闭原则。
4. 同步请求(execute)和异步请求(enqueue)的区别是什么?如何实现线程切换?(线程题)
核心回答点:
- 执行方式 :
- 同步 :阻塞当前线程,在主线程调用会导致 ANR,需在子线程执行,直接返回
Response
。 - 异步 :通过
Dispatcher
调度到线程池(默认ExecutorService
),回调Callback
在子线程,需手动通过Handler
切回主线程。
- 同步 :阻塞当前线程,在主线程调用会导致 ANR,需在子线程执行,直接返回
- 线程管理 :
Dispatcher
控制最大并发请求数(默认 64 个,同一主机 5 个),异步请求通过AsyncCall
封装,放入队列或直接执行。 - 面试陷阱 :避免混淆 "异步回调是否在主线程",OkHttp 不负责线程切换,需开发者自行处理(如
runOnUiThread
)。
5. OkHttp 相比 Volley 或 HttpURLConnection 的优势是什么?(对比题)
核心回答点:
- 性能优化:连接池复用、缓存策略、SPDY/HTTP/2 支持(减少 TCP 连接数),网络请求效率更高。
- 扩展性:拦截器机制灵活,方便添加日志、重试、加密等功能,而 Volley 更适合小量短连接请求。
- 稳定性:内置重试机制(自动处理连接超时、重定向),支持流式响应处理(大文件下载),适合复杂网络场景。
- 面试加分:结合实际项目,说明 OkHttp 在处理高并发、大文件、复杂网络环境下的优势。
6. 如何优化 OkHttp 的网络请求性能?(实战题)
核心回答点:
- 连接池调优 :根据业务场景调整
maxIdleConnections
和keepAliveDuration
(如高频接口增大连接数)。 - 缓存策略 :合理设置缓存有效期(
CacheControl.maxAge
),减少无效网络请求。 - HTTPS 优化 :使用
CertificatePinner
固定证书,避免 SSL 握手耗时;启用 HTTP/2(需服务端支持)。 - 并发控制 :通过
Dispatcher.setMaxRequests
和setMaxRequestsPerHost
限制并发,避免资源耗尽。
基础篇