2. 自定义拦截器基础实现
在 OkHttp 的拦截器链中,每个拦截器都是一个 Interceptor 接口的实现。它负责在请求发送前或响应返回后执行自定义逻辑,例如添加公共头、日志记录、参数加密、隐私字段过滤等。本章将从接口契约、调用链路、请求/响应修改三方面讲解基础实现方式。
2.1 Interceptor 接口与 chain.proceed() 原理
OkHttp 定义了一个核心接口 okhttp3.Interceptor,其中只有一个方法:
public interface Interceptor {
@NonNull Response intercept(@NonNull Chain chain) throws IOException;
}
参数 Chain 代表整个拦截器链。调用 chain.proceed(request) 会将当前 Request 传递给链中的下一个拦截器,并最终执行网络 I/O 或缓存命中,返回 Response。在 intercept 方法中,开发者可以在调用 proceed 之前修改请求,在调用之后修改响应。
关键语义:每个拦截器应当恰好调用一次
chain.proceed(),否则会断开拦截器链,导致请求无法完成或响应无法返回。
2.2 自定义拦截器的实现流程
实现一个自定义拦截器通常包含以下步骤:
- 实现 Interceptor 接口:创建类实现
intercept(Chain)方法。 - 获取原始请求:通过
chain.request()拿到不可变的Request对象。 - 修改请求(可选):使用
Request.Builder在原有请求基础上添加/删除 Header、修改 URL、替换 Body 等。 - 调用
chain.proceed(newRequest):传入修改后的请求,获得响应。 - 修改响应(可选):使用
Response.Builder修改响应头、状态码、Body 等。 - 返回最终 Response。
一个完整的隐私过滤拦截器示例(过滤掉请求中的用户手机号 Header):
public class PrivacyFilterInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 1. 获取原始请求
Request originalRequest = chain.request();
// 2. 检查并移除隐私 Header
Request.Builder builder = originalRequest.newBuilder();
if (originalRequest.header("X-User-Phone") != null) {
builder.removeHeader("X-User-Phone")
.addHeader("X-Phone-Filtered", "true");
}
// 3. 构建新请求
Request filteredRequest = builder.build();
// 4. 传递给下一拦截器
Response response = chain.proceed(filteredRequest);
// 5. 修改响应:添加一条标记
return response.newBuilder()
.header("X-Privacy-Filtered", "true")
.build();
}
}
2.3 Request/Response 的可修改范围
通过 Request.Builder 和 Response.Builder 只能创建新的不可变对象,无法直接修改原始对象。以下是可修改的字段清单:
| 对象 | 可修改项 | 示例方法 |
|---|---|---|
| Request.Builder | URL、Method、Headers、Body、Tag、CacheControl | .url()、.header(name, value)、.removeHeader(name)、.post(body) |
| Response.Builder | Code、Message、Headers、Body、Protocol、Handshake、CacheResponse | .code()、.message()、.header(name, value)、.body(responseBody) |
注意:如果尝试修改
Request 的 URL 或 Method,可能会导致路由或缓存策略改变,务必在理解 OkHttp 内部机制后操作。对于 Body 的替换,需确保新旧 Body 的 Content-Type 一致或适配。
2.4 内联流程图
下图展示了自定义拦截器在 OkHttp 链中的位置以及请求/响应修改的典型路径:
2.5 调试与验证
在集成自定义拦截器后,建议通过以下方式验证隐私过滤是否生效:
- 日志拦截器:在自定义拦截器之后添加一个日志拦截器,打印最终发出的请求 Header 和响应 Header。
- 单元测试:使用
MockWebServer模拟服务器,断言请求中不包含敏感字段。 - 抓包工具:通过 Charles / Wireshark 确认实际网络包中已移除隐私数据。
2.6 常见错误与最佳实践
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
未调用 chain.proceed() | 拦截器直接返回 new Response.Builder()...build() | 必须调用一次 chain.proceed(),否则后续拦截器不可达 |
多次调用 chain.proceed() | 导致重复网络请求 | 只调用一次,将结果暂存后返回 |
| 修改后未重新构建 Header | 直接使用 request.header() 无法移除 | 使用 newBuilder().removeHeader().build() |
| 修改了 URL 但未考虑缓存 | Url 变更可能导致缓存 key 不匹配 | 若需修改 URL,考虑在应用层拦截器而非网络层 |
2.7 性能与稳定性优化
- 避免在拦截器中执行耗时 I/O(如磁盘写入、网络调用),否则会阻塞线程池。
- 拦截器实例只创建一次,在
OkHttpClient.Builder中复用,减少 GC。 - 对 Body 的修改(如替换为脱敏文本)务必保持
Content-Length一致或使用BufferedSink流式写入。 - 异常处理:在
intercept方法中捕获IOException并决定是否重试或返回 fallback 响应。
建议:始终将隐私过滤拦截器放在应用层拦截器中(通过
addInterceptor() 添加),以确保在所有重定向和重试中生效。网络层拦截器(通过 addNetworkInterceptor())在失败重试时会再次触发,可能导致重复过滤。