1. 认识OkHttp拦截器链:拦截器分类、调用顺序与Interceptor接口定义

1.1 拦截器在OkHttp中的角色

OkHttp的拦截器(Interceptor)是框架最核心的扩展机制。它采用责任链(Chain of Responsibility)设计模式,允许开发者在HTTP请求/响应的关键路径中插入自定义逻辑。每一次网络调用都会沿着一个有序的拦截器链传递,每个拦截器可以观察、修改、甚至短路请求与响应。

关键思路:拦截器不仅是“钩子”,更是OkHttp实现重试、重定向、缓存、桥接、协议层适配等功能的基础。理解拦截器链的分类和调用顺序,是设计动态缓存拦截器的首要前提。

1.2 拦截器分类

OkHttp将拦截器分为两大类,它们在链中的位置和行为有本质区别:

分类 注册方式 执行位置 典型行为
应用拦截器 OkHttpClient.Builder.addInterceptor() 位于链的首尾两端 操作原始Request/Response,不感知重定向、重试
网络拦截器 OkHttpClient.Builder.addNetworkInterceptor() 位于重试/重定向之后 看到真正的网络请求,可监控流量、压缩
易混淆点:同一个拦截器实现如果同时作为应用和网络拦截器注册,将执行两次。网络拦截器看到的Response是完整的,包含Cache-Control等头部,而应用拦截器看到的Response可能来自缓存。

1.3 拦截器调用顺序与责任链

OkHttp内置了一套默认拦截器,它们按照固定顺序排列。整个链的调度逻辑决定了每个拦截器何时被调用。以下是内置链的典型流程:

责任链传递规则:

Client(应用拦截器) → RetryAndFollowUpInterceptor → BridgeInterceptor
→ CacheInterceptor → ConnectInterceptor → Network(网络拦截器)
→ CallServerInterceptor → 网络拦截器返回 → CacheInterceptor更新
→ BridgeInterceptor处理 → RetryAndFollowUpInterceptor检查 → 应用拦截器返回

在RealCall.getResponseWithInterceptorChain()方法中,通过构造一个Interceptor.Chain的实现类RealInterceptorChain,依次调用每个拦截器的intercept()方法。每个拦截器通过chain.proceed(request)将请求传递给下一个节点。

1.4 Interceptor接口定义

所有拦截器必须实现okhttp3.Interceptor接口,该接口只定义一个方法:

public interface Interceptor {
    Response intercept(Chain chain) throws IOException;

    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;
        int connectTimeoutMillis();
        void withConnectTimeout(int timeout, TimeUnit unit);
        int readTimeoutMillis();
        void withReadTimeout(int timeout, TimeUnit unit);
        int writeTimeoutMillis();
        void withWriteTimeout(int timeout, TimeUnit unit);
    }
}

核心方法intercept()接收一个Chain对象,通过调用chain.proceed(request)将请求传递下去并获取响应。开发者的所有自定义逻辑(如缓存读取、重试判断、日志打印)都需要在这个方法内完成。

接口设计要点:Chain接口除了proceed()之外,还提供了超时管理方法。动态缓存拦截器通常只需要重写intercept()方法,并在合适时机调用chain.proceed()。

1.5 内置拦截器注册顺序与作用

OkHttp在RealCall中按以下顺序构建拦截器列表(从左到右执行):

// 源码位置:okhttp3.internal.http.RealCall
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());               // ① 用户自定义应用拦截器
interceptors.add(new RetryAndFollowUpInterceptor(client)); // ② 重试与重定向
interceptors.add(new BridgeInterceptor(client.cookieJar()));// ③ 桥接(添加请求头/压缩)
interceptors.add(new CacheInterceptor(client.cache()));    // ④ 缓存
interceptors.add(new ConnectInterceptor(client));          // ⑤ 连接
if (forWebSocket) {
    interceptors.add(new WebSocketInterceptor(client));    // WebSocket专属
} else {
    interceptors.addAll(client.networkInterceptors());     // ⑥ 用户自定义网络拦截器
}
interceptors.add(new CallServerInterceptor(forWebSocket)); // ⑦ 实际网络I/O

理解这个顺序至关重要:缓存拦截器(CacheInterceptor)位于应用拦截器之后、网络拦截器之前,这意味着任何缓存逻辑必须与重试、桥接等行为协同工作。

1.6 拦截器链结构图

下面以SVG图展示内置拦截器形成的完整责任链,以及用户自定义拦截器的插入位置:

OkHttp 拦截器责任链结构 应用拦截器区域 核心内置链 网络拦截器区域 应用拦截器 (可插入多个) 重试与重定向 interceptor 桥接拦截器 (header/压缩) 缓存拦截器 (读取/更新缓存) 连接拦截器 (建立TCP连接) 网络拦截器 (可插入多个) 网络I/O拦截器 (实际读写) Response 返回路径 责任链执行规则说明 ① 应用拦截器先执行,然后进入内置链(重试→桥接→缓存→连接) ② 网络拦截器在连接建立之后、实际I/O之前执行 ③ 响应反向经过相同的拦截器,顺序是链的逆序 ④ 每个拦截器可以决定是否调用 chain.proceed() 来影响后续拦截器 ⑤ 动态缓存拦截器通常作为应用拦截器注册,拦截 request/response 对

1.7 动态缓存拦截器的切入点

通过上述结构我们可以得出设计动态缓存拦截器的两个关键切入点:

  1. 作为应用拦截器注册:可以控制是否从本地缓存直接返回响应而不触发网络请求;并且在获得响应后可以更新缓存。缺点是看不到重定向后的最终URL。
  2. 在CacheInterceptor前后介入:利用OkHttp自带的CacheStrategy,在合适的时机更新或绕过缓存策略。这通常需要理解CacheInterceptor内部的CacheStrategy判断逻辑。

1.8 常见错误与避坑指南

  • 错误:在拦截器中没有调用 chain.proceed(),导致链中断,下游拦截器(包括实际的网络请求)永远无法执行。
  • 错误:对Response做修改后没有正确关闭旧的Response body,导致资源泄漏或Java内存泄漏。
  • 错误:在应用拦截器中使用response.cacheResponse(),以为判断了缓存命中,但在应用拦截器中这个值可能为空,因为缓存响应还未被CacheInterceptor填充。
  • 建议:使用@Nullable标注与response.cacheResponse()、response.networkResponse()相关的方法,避免空指针。

1.9 小结