4. 自定义 Interceptor 实现:实现 Interceptor 接口、intercept 方法、Chain.proceed 调用
4.1 原理概述
OkHttp 的拦截器链(Interceptor Chain)采用责任链模式。每个实现了 Interceptor 接口的对象都可以在请求发出前 拦截 并 修改 请求,在响应返回后 处理 响应数据。自定义拦截器是承载埋点状态嵌入的最小工作单元——你可以在 intercept() 方法中注入上下文(如用户ID、会话凭证、请求耗时标记等),再通过 chain.proceed() 将请求传递给链中下一个拦截器。
| 核心接口 / 方法 | 作用 |
|---|---|
Interceptor 接口 |
定义拦截器契约,仅包含一个方法 intercept(Chain) |
Chain (拦截器链) |
持有请求对象、连接信息,提供 proceed(Request) 执行下一个拦截器 |
request() (Chain 方法) |
获取当前待处理的 Request 对象 |
proceed(Request) |
将请求继续沿链向下传递,返回 Response |
4.2 实现流程
- 创建类 —— 实现
okhttp3.Interceptor接口。 - 重写
intercept(Chain)—— 在其中获取请求、注入埋点状态、调用chain.proceed()并捕获响应。 - 返回
Response—— 处理完响应(如添加响应头、记录日志)后必须返回。 - 注册到
OkHttpClient—— 根据需求添加到应用拦截器列表或网络拦截器列表。
关键提醒: 被拦截的 Request 对象是不可变的(immutable),必须通过其
newBuilder() 创建新的 Request 对象,再传给 proceed()。
4.3 代码实现示例
public class StateEmbeddingInterceptor implements Interceptor {
private final String userId;
private final String sessionToken;
public StateEmbeddingInterceptor(String userId, String sessionToken) {
this.userId = userId;
this.sessionToken = sessionToken;
}
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
// 1. 获取原始请求
Request originalRequest = chain.request();
// 2. 构造携带埋点状态的新请求
Request modifiedRequest = originalRequest.newBuilder()
.header("X-User-Id", userId)
.header("X-Session-", sessionToken)
.header("X-Embedded-Ts", String.valueOf(System.currentTimeMillis()))
.build();
// 3. 传递给下一个拦截器
Response response = chain.proceed(modifiedRequest);
// 4. 对响应进行二次处理(可选)
// 例如:读取响应埋点标识、记录耗时
Response.Builder responseBuilder = response.newBuilder()
.header("X-Monitored", "true");
return responseBuilder.build();
}
}
4.4 注册到 OkHttpClient
将自定义拦截器添加到客户端时,需区分 应用拦截器(addInterceptor)与 网络拦截器(addNetworkInterceptor):
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new StateEmbeddingInterceptor("uid_1001", "_abc123"))
// 或 .addNetworkInterceptor(...)
.build();
建议: 埋点状态嵌入通常放在应用拦截器层(addInterceptor),因为它在重试和重定向之前就执行,且对每一个应用发出的请求都生效。
4.5 拦截器链调用示意(SVG)
4.6 常见错误与避坑
- 忘记调用
chain.proceed()—— 如果不调用,请求将永远得不到响应,导致IOException或界面卡死。 - 修改了原始 Request 对象 —— 直接对
chain.request()做 setter 会抛UnsupportedOperationException,必须使用newBuilder()创建副本。 - 在拦截器中抛出未捕获的异常 —— 拦截器运行在 OkHttp 线程池中,应妥善处理
IOException并向外抛出,以免破坏链状态。 - 与网络拦截器混淆 —— 网络拦截器会收到重定向和重试后的请求,可能导致埋点状态重复添加或丢失,需根据需求选择注册方法。
4.7 性能与稳定性建议
性能关注点: 不要在
intercept() 中执行耗时操作(如文件读写、复杂加解密),否则会阻塞 OkHttp 的异步分发。若必须执行,请考虑使用自定义线程池或 异步拦截器模式(不过 OkHttp 官方不推荐过度复杂的异步拦截器)。
- 埋点头字段保持轻量(如用户ID、时间戳),避免携带大对象(base64 大图、序列化 JSON)。
- 对
System.currentTimeMillis()采集的埋点时间戳,注意时区统一(推荐 UTC);也可改为System.nanoTime()计算相对耗时。 - 建议为自定义拦截器添加日志开关,在调试阶段输出拦截前后的请求/响应头,方便定位问题。