OkHttp 拦截器体系概览
OkHttp 的拦截器(Interceptor)是整个网络请求管线的核心扩展点。它将请求的发起、预处理、重试、重定向、响应后处理等阶段拆解为可插拔的节点,形成一个责任链(Chain of Responsibility)模型。理解拦截器链的结构、两种拦截器的定位以及执行顺序,是实现隐私过滤的第一个知识门槛。
1.1 拦截器链模型
OkHttp 内部维护了一个有序的 Interceptor 列表。当一个请求发起时,OkHttp 会按添加顺序依次调用每个拦截器的 intercept(Chain chain) 方法。每个拦截器在执行自己的逻辑后,必须调用 chain.proceed(request) 将请求交给链中的下一个拦截器,直到最后一个拦截器完成实际的网络 I/O。响应数据再沿着调用栈反向传播回调用方。
这一模型带来了两个重要的特性:
- 栈式执行:请求从链头走向链尾,响应从链尾返回链头,形成类似函数调用的栈结构。
- 拦截器可中断:任何拦截器都可以在调用
proceed前决定不继续传递请求(例如返回缓存响应或抛出异常),从而中断整个链条。
💡 核心概念:chain.proceed() 是连接上下游拦截器的唯一通道。在它之前执行的代码属于“请求前置处理”,在它之后执行的代码属于“响应后置处理”。
1.2 应用拦截器 vs. 网络拦截器
OkHttp 提供了两种注册拦截器的方式:addInterceptor() 与应用拦截器(Application Interceptor)关联,addNetworkInterceptor() 与网络拦截器(Network Interceptor)关联。两种拦截器在链条中的位置和语义有本质区别。
| 对比维度 | 应用拦截器 | 网络拦截器 |
|---|---|---|
| 注册方法 | OkHttpClient.Builder.addInterceptor() |
OkHttpClient.Builder.addNetworkInterceptor() |
| 调用位置 | 位于拦截器链最外层,最先处理请求,最后处理响应 | 位于应用拦截器之后、核心网络 I/O 之前 |
| 重定向处理 | 不感知重定向,只看到原始请求和最终响应 | 感知每次重定向,可能看到多次网络请求 |
| 重试与缓存 | 位于重试和重定向外部,一次拦截对应一次业务调用 | 位于重试和重定向内部,每次网络尝试都会触发 |
| 典型的应用场景 | 统一添加 Header、日志记录、隐私过滤、 注入 | 网络级监控、请求/响应压缩、Gzip 解压、重试逻辑 |
实践建议:
- 隐私过滤逻辑(如移除手机号、邮箱、设备 ID)应放在 应用拦截器 中。因为应用拦截器只触发一次,且不会感知重定向后的中间请求,过滤逻辑更少出错。
- 网络拦截器适合需要观察每次实际网络往返的场景,例如打印状态码、追踪重定向路径、记录传输字节数。
1.3 拦截顺序与影响
拦截器的添加顺序直接决定了数据的处理流程。错误的顺序可能导致隐私数据在日志中泄露,或者过滤规则被后续拦截器覆盖。
1.3.1 添加顺序
应用拦截器和网络拦截器分别按照 addInterceptor 或 addNetworkInterceptor 的调用顺序组成内部链表。同一个客户端对象上多次调用同类型注册方法,后添加的拦截器更靠近链头(优先执行请求前置逻辑)。
// 顺序示例:应用拦截器 A -> B -> C
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptorA) // 最先处理请求,最后处理响应
.addInterceptor(interceptorB)
.addInterceptor(interceptorC) // 最后处理请求,最先处理响应
.addNetworkInterceptor(netA) // 网络拦截器顺序以此类推
.addNetworkInterceptor(netB)
.build();
1.3.2 执行顺序的时间线
为了更清晰地可视化执行顺序,以下是一个典型的请求-响应时间线(假设应用拦截器 1 个,网络拦截器 1 个):
请求方向(自上而下):
应用拦截器.intercept() 开始
→ 应用拦截器前置代码(可修改请求)
→ 网络拦截器.intercept() 开始
→ 网络拦截器前置代码
→ 核心网络 I/O(发送请求、接收响应)
→ 网络拦截器后置代码(可修改响应)
→ 网络拦截器返回响应
→ 应用拦截器后置代码(可修改响应)
→ 应用拦截器返回响应
响应方向(自下而上)
1.3.3 顺序相关的常见错误
⚠️ 常见错误 1:日志拦截器放在隐私过滤拦截器之前
如果日志拦截器(例如 HttpLoggingInterceptor)先执行 chain.proceed(),而隐私过滤拦截器后执行,那么日志拦截器会输出包含隐私数据的原始请求和响应。隐私过滤将无效。
✅ 正确做法:始终将 隐私过滤拦截器注册在日志拦截器之前(即先 addInterceptor 过滤,后 addInterceptor 日志),确保链条中日志节点看到的是已净化后的数据。
⚠️ 常见错误 2:错误认为网络拦截器和应用拦截器执行次数相同
当请求发生重定向时,应用拦截器只执行一次,而网络拦截器会执行多次(每次重定向都触发一次网络调用)。如果在网络拦截器中执行有副作用的操作(如统计请求次数、修改持久化数据),可能导致统计偏差。
1.4 核心 API 快速参考
实现自定义拦截器需要实现 Interceptor 接口,关键方法和参数如下:
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
// 其他方法:connection(), call(), connectTimeoutMillis(), readTimeoutMillis() 等
}
}
一个最简单的拦截器示例(仅打印 URL,不做修改):
public class UrlLoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request(); // 获取当前请求
System.out.println("[拦截器] 请求 URL: " + request.url());
Response response = chain.proceed(request); // 向下传递
System.out.println("[拦截器] 响应 code: " + response.code());
return response;
}
}
1.5 本章小结
- OkHttp 的拦截器基于责任链模式,
chain.proceed()是串联上下游的核心方法。 - 应用拦截器(
addInterceptor)在重定向和重试外部,适合做一次性的隐私过滤;网络拦截器(addNetworkInterceptor)在内部,每次网络往返都会触发。 - 拦截器注册顺序直接影响过滤效果:隐私过滤拦截器必须早于日志、上报等拦截器注册。
后面章节将基于本章的体系结构,深入实现各类型的隐私过滤拦截器,并给出完整的代码工程化方案。