第2章 OkHttp缓存拦截器核心接口与工作机制:源码分析 Interceptor.Chain、CacheStrategy、CacheControl
本章要点:从源码层剖析 OkHttp 拦截器链的三大核心组件——
Interceptor.Chain、CacheStrategy、CacheControl,理解它们如何协作实现缓存命中、失效判断与请求再验证,并掌握拦截器调用链的完整执行路径。
2.1 拦截器链的整体架构
OkHttp 的缓存能力建立在一套分层拦截器架构之上。每个网络请求在发出前会依次穿过多个拦截器,其中 CacheInterceptor 是决定「是否走缓存」的关键一环。理解它必须从最顶层的 Interceptor.Chain 接口开始。
| 组件 | 角色 | 源码包路径 |
|---|---|---|
| Interceptor.Chain | 拦截器链调度接口,控制请求传递与响应回溯 | okhttp3.Interceptor |
| CacheStrategy | 缓存策略决策器,判定缓存是否可用、是否需要网络请求 | okhttp3.internal.cache.CacheStrategy |
| CacheControl | 解析与构建 HTTP Cache-Control 指令的不可变对象 | okhttp3.CacheControl |
| CacheInterceptor | 拦截器链中的缓存处理节点,组合上述三个组件 | okhttp3.internal.cache.CacheInterceptor |
2.2 Interceptor.Chain 接口:请求传递的调度中枢
Interceptor.Chain 定义了两个核心方法,是所有拦截器编排的基础:
public interface Interceptor {
Response intercept(Chain chain);
interface Chain {
Request request();
Response proceed(Request request);
Connection connection();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
每次调用 chain.proceed(request) 时,RealInterceptorChain 会将请求递交给列表中的下一个拦截器,同时保留响应回溯的能力。缓存拦截器正是利用这一机制,在适当时机直接返回缓存响应,从而短路后续的网络拦截器。
关键设计:
proceed() 是同步调用,每个拦截器在调用前后可分别执行「请求前处理」和「响应后处理」。CacheInterceptor 在调用 proceed() 之前检查缓存,在调用之后更新缓存。
2.3 CacheStrategy:缓存命中的决策引擎
CacheStrategy 是纯计算类,不持有状态。它的核心方法 get() 接收 Request 和 Response(缓存条目),返回一个 CacheStrategy 实例,其中包含两个字段:
public final class CacheStrategy {
public final @Nullable Request networkRequest; // null 表示不需网络请求
public final @Nullable Response cacheResponse; // null 表示无可用缓存
// 核心工厂方法
public static CacheStrategy get(Request request, Response cacheResponse) {
// 根据 Cache-Control、Date、Expires、Last-Modified、ETag 等头部计算
// 返回值有三种组合:
// 1. networkRequest != null, cacheResponse == null → 仅网络请求
// 2. networkRequest == null, cacheResponse != null → 仅缓存响应(缓存命中)
// 3. networkRequest != null, cacheResponse != null → 条件请求(再验证)
}
}
三种决策结果:
- 仅网络请求:无缓存 / 缓存已过期且无验证条件 → 走完整网络链路
- 仅缓存响应:缓存新鲜(max-age 有效期内)→ 直接返回缓存,完全不发起网络请求
- 条件请求:缓存过期但有 ETag/Last-Modified → 发起 GET 请求携带
If-None-Match或If-Modified-Since
2.4 CacheControl:HTTP 缓存指令的封装
CacheControl 解析 Cache-Control 与 Pragma 头部,对外提供布尔/整型查询方法。它同时服务于请求指令(客户端控制)和响应指令(服务端控制)。
public final class CacheControl {
public static final CacheControl FORCE_NETWORK = new CacheControl.Builder()
.noCache().build(); // 等价于 Cache-Control: no-cache
public static final CacheControl FORCE_CACHE = new CacheControl.Builder()
.onlyIfCached().maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS).build();
private final boolean noCache; // 每次都必须验证
private final boolean noStore; // 不允许缓存任何内容
private final int maxAgeSeconds; // 缓存新鲜期(秒)
private final int maxStaleSeconds; // 过期后仍可服侍的最大秒数
private final int minFreshSeconds; // 要求剩余新鲜时间至少为…
private final boolean onlyIfCached; // 只接受缓存,不发起网络
private final boolean mustRevalidate; // 服务端要求必须验证
// 解析头部
public static CacheControl parse(Headers headers) { /* … */ }
// 构建请求/响应指令
public static final class Builder { /* … */ }
}
| 指令 | 作用于 | 含义 | CacheInterceptor 行为 |
|---|---|---|---|
no-cache | 请求 / 响应 | 每次必须向源服务器验证 | 强制走 proceed(),但可携带条件头 |
no-store | 请求 / 响应 | 禁止写入缓存 | 不读取也不写入磁盘缓存 |
max-age=60 | 响应 | 60 秒内为新鲜 | 60 秒内直接返回缓存,不发请求 |
only-if-cached | 请求 | 只接受缓存,无缓存则返回 504 | 缓存未命中时直接构造 504 响应 |
max-stale=30 | 请求 | 过期后 30 秒内仍可接受 | 扩展新鲜期,减少网络请求 |
2.5 拦截器调用链与三组件协作流程
下图完整展示了从请求发起经 RealInterceptorChain 调度,逐层穿过各拦截器,并在 CacheInterceptor 处利用 CacheStrategy + CacheControl 做出缓存决策的全过程。
2.6 源码关键流程解析
在 CacheInterceptor.intercept() 内部,三组件的协作顺序非常清晰:
public Response intercept(Chain chain) throws IOException {
// 1. 从缓存中取出候选响应
Response cacheCandidate = cache != null ? cache.get(chain.request()) : null;
// 2. 构建 CacheStrategy —— 传入当前请求 + 缓存候选响应
CacheStrategy strategy = CacheStrategy.get(chain.request(), cacheCandidate);
// 3. 根据策略结果判断
if (strategy.networkRequest == null && strategy.cacheResponse == null) {
// 既无网络请求也无缓存 → 504
return new Response.Builder()
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(ResponseBody.create(null, ""))
.build();
}
if (strategy.networkRequest == null) {
// 无需网络请求 → 直接返回缓存(缓存命中)
return strategy.cacheResponse.newBuilder()
.cacheResponse(stripBody(strategy.cacheResponse))
.build();
}
// 4. 需要网络请求 —— 继续传递拦截器链
Response networkResponse;
try {
networkResponse = chain.proceed(strategy.networkRequest);
} catch (IOException e) {
// 网络失败时,若有可用过期缓存则降级使用
if (cacheCandidate != null) throw e;
throw e;
}
// 5. 响应回溯后更新缓存
if (cache != null) {
if (HttpHeaders.promisesBodyForResponse(networkResponse)) {
CacheStrategy.Companion.trackResponse(cache, strategy);
}
}
return networkResponse;
}
2.7 调试与日志验证
启 OkHttp 的日志拦截器可以直观看到缓存决策过程:
// 添加日志拦截器观察缓存行为
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging) // 应用层:看到请求/响应头部
.cache(new Cache(cacheDir, 10L * 1024 * 1024))
.build();
// 日志输出示例(缓存命中时):
// → GET https://api.example.com/users
// ← 200 (cache) → 注意 "(cache)" 标记表示未发起网络请求
// → Cache-Control: max-age=3600
// ← Date: Thu, 01 Jan 2025 12:00:00 GMT
调试技巧:使用
CacheControl.FORCE_NETWORK 可临时绕过缓存验证网络连通性;使用 CacheControl.FORCE_CACHE 可测试离线缓存表现。
2.8 常见错误与稳定性优化
| 错误模式 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 缓存永远不命中 | 每条日志都无 (cache) 标记 | 响应头缺少 Cache-Control 或设置了 no-store | 在服务端添加 Cache-Control: max-age=300 |
| 缓存数据陈旧 | 展示过期内容 | max-stale 设置过大或服务端 must-revalidate 被忽略 | 合理配置 max-age 并启用 must-revalidate |
| 条件请求频繁 | 每次仍发起网络请求 | 响应缺少 ETag 或 Last-Modified | 服务端添加验证器头部,减少传输成本 |
| 缓存写回异常 | 日志显示 504 或缓存文件损坏 | 磁盘空间满或 Cache 目录权限错误 | 监控 Cache.size(),捕获 IOException 降级 |
性能红线:切勿在
CacheInterceptor 之前添加大量耗时拦截器,否则即使缓存命中也会引入不必要的延迟。推荐的拦截器顺序:日志 → 重试 → 缓存 → 连接 → 网络。
2.9 本章总结
- Interceptor.Chain 是拦截器编排的基石,
proceed()控制请求传递与响应回溯; - CacheStrategy 是纯函数式决策器,三种输出对应三种网络行为;
- CacheControl 封装 HTTP 缓存指令,同时服务客户端与服务端;
- 三者通过 CacheInterceptor 整合,在调用链中实现短路网络、条件验证、缓存更新完整闭环。