2. Interceptor 接口与 Chain 接口核心方法:intercept()、request()、proceed()、response() 作用与调用时机
2.1 两个核心接口的设计意图
OkHttp 的拦截器(Interceptor)体系是整个网络层模块化的基石。Interceptor 接口定义了拦截器需要实现的拦截逻辑,
Chain 接口则封装了整个请求链的串联机制。一次网络请求从创建到响应,会依次穿过多个拦截器,
每个拦截器通过 Chain 获得当前请求、将请求传递给下一个拦截器、并最终拿到响应。
关键概念:
Chain 是连接前后两个拦截器的纽带,它保证了请求-响应的流水线处理模型。
2.2 核心方法详解
| 方法 | 所属接口 | 参数 | 返回值 | 调用时机 | 核心作用 |
|---|---|---|---|---|---|
intercept(Chain chain) |
Interceptor | Chain |
Response |
每个拦截器的入口 | 实现拦截逻辑:读取/修改请求、转发请求、处理响应 |
request() |
Chain | 无 | Request |
在 intercept() 内调用,获取当前待处理的 HTTP 请求对象 |
提供原始请求或经前序拦截器修改后的请求 |
proceed(Request request) |
Chain | Request |
Response |
在 intercept() 内调用,将请求传递给下一个拦截器并阻塞等待响应 |
推动拦截器链前进,返回下游处理后的最终响应 |
response() |
Chain(间接通过返回值获得) | 无 | Response |
proceed() 返回后获得,或者在 intercept() 尾部返回 |
携带服务器的最终响应数据,可在此进行拆包、缓存、重试等操作 |
2.3 调用时机与流程
下图展示了三个拦截器组成的典型链,清晰呈现了各方法的调用顺序与数据流向。
2.4 实现代码示例
下面展示一个自定义日志拦截器的完整实现,它重写了 intercept() 并使用了所有核心方法:
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
// 1. 获取当前请求
Request request = chain.request();
long startTime = System.nanoTime();
System.out.println("发送请求: " + request.method() + " " + request.url());
// 2. 将请求传递给下一个拦截器,并等待响应(此处会阻塞)
Response response = chain.proceed(request);
long elapsedTime = (System.nanoTime() - startTime) / 1_000_000;
System.out.println("接收响应: " + response.code()
+ " (耗时 " + elapsedTime + " ms)");
// 3. 返回响应(可以在此对响应进行包装、缓存等)
return response;
}
}
2.5 proceed() 的内部原理
调用 chain.proceed(request) 时,实际上触发了递归式的调用:
- OkHttp 的
RealInterceptorChain内部保存了一个拦截器列表和当前索引。 proceed()会创建一个新的RealInterceptorChain(索引 +1),并调用下一个拦截器的intercept()方法。- 最后一个拦截器(如网络拦截器或连接拦截器)会真正执行 HTTP 调用(如 Socket 读写)并返回
Response。
2.6 常见错误与警告
高频错误 1:多次调用 proceed()
同一个
同一个
Chain 对象只能调用一次 proceed(),否则会抛出 IllegalStateException。需要重试时应克隆请求或创建新的 Chain。
高频错误 2:不调用 proceed()
如果拦截器没有调用
如果拦截器没有调用
proceed(),后续所有拦截器和实际网络请求都不会执行,最终会因缺少响应而抛出空指针或异常。除非你明确希望阻断请求(如命中本地缓存),否则必须调用。
高频错误 3:错误修改请求/响应对象
Request 和 Response 的 Builder 每次调用 build() 都会创建新对象,必须将新对象传入 proceed() 或返回。直接修改原对象的字段不会生效。
2.7 性能与稳定性优化建议
| 场景 | 建议 | 理由 |
|---|---|---|
| 耗时操作(如加解密、图片压缩) | 使用 chain.call().request() 区分异步回调,或将操作放在子线程 |
proceed() 会阻塞当前线程,主线程耗时过长导致 ANR |
| 拦截器顺序 | 日志、重试等拦截器放在应用拦截器(addInterceptor),认证、压缩等放在网络拦截器(addNetworkInterceptor) | 网络拦截器只执行一次,且能查看完整网络数据;应用拦截器会因重定向重复执行 |
| 响应流复用 | 不要在 intercept() 内消费 Response 的 Body 后再返回 |
Body 只能被消费一次,后续拦截器会读取到空流 |
2.8 调试技巧
在开发阶段,为每个拦截器添加唯一的 UUID 到 Request header 中,可以帮助追踪请求经过的路径:
Request request = chain.request().newBuilder()
.header("X-Trace-Id", UUID.randomUUID().toString())
.build();
然后通过日志或服务端日志即可验证拦截器是否按预期顺序执行。
本章小结:
intercept() 是拦截器的入口,request() 获取当前请求,proceed() 推动链式调用并等待结果,response() 体现为 proceed() 的返回值。理解这四个方法的职责和调用时机,是构建自定义拦截器的基础。