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 调用时机与流程

下图展示了三个拦截器组成的典型链,清晰呈现了各方法的调用顺序与数据流向。

Interceptor Chain 核心方法调用流程 Interceptor 1 intercept(chain) Interceptor 2 intercept(chain) Interceptor 3 intercept(chain) chain.request() proceed(request) proceed(request) proceed() 返回 Response proceed() 返回 Response Response (最终返回) 箭头与时机说明: ① 每个拦截器首先使用 chain.request() 获取当前 Request。 ② 调用 chain.proceed(request) 将 Request 传给下游,并阻塞等待 Response。 ③ 下游拦截器处理完毕后向上游返回 Response,上游可在 intercept() 内对 Response 进行修改或记录。

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() 的返回值。理解这四个方法的职责和调用时机,是构建自定义拦截器的基础。