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图展示内置拦截器形成的完整责任链,以及用户自定义拦截器的插入位置:
1.7 动态缓存拦截器的切入点
通过上述结构我们可以得出设计动态缓存拦截器的两个关键切入点:
- 作为应用拦截器注册:可以控制是否从本地缓存直接返回响应而不触发网络请求;并且在获得响应后可以更新缓存。缺点是看不到重定向后的最终URL。
- 在CacheInterceptor前后介入:利用OkHttp自带的
CacheStrategy,在合适的时机更新或绕过缓存策略。这通常需要理解CacheInterceptor内部的CacheStrategy判断逻辑。
1.8 常见错误与避坑指南
- 错误:在拦截器中没有调用
chain.proceed(),导致链中断,下游拦截器(包括实际的网络请求)永远无法执行。 - 错误:对
Response做修改后没有正确关闭旧的Response body,导致资源泄漏或Java内存泄漏。 - 错误:在应用拦截器中使用
response.cacheResponse(),以为判断了缓存命中,但在应用拦截器中这个值可能为空,因为缓存响应还未被CacheInterceptor填充。 - 建议:使用
@Nullable标注与response.cacheResponse()、response.networkResponse()相关的方法,避免空指针。