OkHttp 拦截器体系概览

OkHttp 的拦截器(Interceptor)是整个网络请求管线的核心扩展点。它将请求的发起、预处理、重试、重定向、响应后处理等阶段拆解为可插拔的节点,形成一个责任链(Chain of Responsibility)模型。理解拦截器链的结构、两种拦截器的定位以及执行顺序,是实现隐私过滤的第一个知识门槛。

1.1 拦截器链模型

OkHttp 内部维护了一个有序的 Interceptor 列表。当一个请求发起时,OkHttp 会按添加顺序依次调用每个拦截器的 intercept(Chain chain) 方法。每个拦截器在执行自己的逻辑后,必须调用 chain.proceed(request) 将请求交给链中的下一个拦截器,直到最后一个拦截器完成实际的网络 I/O。响应数据再沿着调用栈反向传播回调用方。

这一模型带来了两个重要的特性:

  • 栈式执行:请求从链头走向链尾,响应从链尾返回链头,形成类似函数调用的栈结构。
  • 拦截器可中断:任何拦截器都可以在调用 proceed 前决定不继续传递请求(例如返回缓存响应或抛出异常),从而中断整个链条。

💡 核心概念:chain.proceed() 是连接上下游拦截器的唯一通道。在它之前执行的代码属于“请求前置处理”,在它之后执行的代码属于“响应后置处理”。

OkHttp 拦截器链模型结构图 OkHttp 拦截器链模型(责任链) 应用拦截器 #1 应用拦截器 #2 网络拦截器 #1 网络拦截器 #2 网络拦截器 #N proceed() OkHttp 核心(网络 I/O) 服务器 实际 HTTP 请求/响应 响应反向传播 应用拦截器 网络拦截器 核心 I/O 响应路径

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)在内部,每次网络往返都会触发。
  • 拦截器注册顺序直接影响过滤效果:隐私过滤拦截器必须早于日志、上报等拦截器注册。

后面章节将基于本章的体系结构,深入实现各类型的隐私过滤拦截器,并给出完整的代码工程化方案。