2. 缓存策略模式详解:CacheStrategy决策树,CacheControl指令解析,强制缓存与协商缓存
2.1 核心概念:强制缓存与协商缓存
HTTP 缓存机制是 OkHttp 离线缓存策略的根基,它分为两种核心模式:强制缓存(强缓存)与 协商缓存(弱缓存)。理解这两者的区别,是掌握 CacheStrategy 决策树的前提。
- 强制缓存:客户端直接从本地缓存中读取数据,不与服务器发生任何网络交互。状态码表现为
200 (from cache)。只要缓存未过期(由max-age控制),OkHttp 将直接返回缓存对象。 - 协商缓存:当强制缓存失效或显式要求验证时,客户端会向服务器发送验证请求(携带
If-None-Match或If-Modified-Since)。若资源未变更,服务器返回304 Not Modified,客户端继续使用缓存;若已变更,服务器返回完整的200 OK响应。
| 特征 | 强制缓存 | 协商缓存 |
|---|---|---|
| 触发条件 | 缓存未过期,max-age > 0 |
缓存过期,no-cache,验证头存在 |
| HTTP 状态码 | 200 (from cache) |
304 Not Modified 或 200 OK |
| 优势 | 零网络开销,毫秒级响应 | 保证数据新鲜,304 响应不含 body 节省带宽 |
| 劣势 | 服务器更新后客户端可能暂存过期数据 | 至少有一次网络往返延迟 |
| OkHttp 实现 | CacheControl.Builder().maxAge(10, MINUTES) |
CacheControl.Builder().noCache() / 自动条件请求 |
2.2 CacheControl 指令深度解析
OkHttp 提供了 CacheControl 类,用于在请求和响应中精确控制缓存行为。以下是核心指令及其在 OkHttp API 中的映射关系:
max-age(seconds, unit):缓存的最大存活时间。响应经过此时间后即视为过期。noCache():禁止强制缓存,要求每次使用缓存前都必须向服务器验证其新鲜度(协商缓存)。noStore():完全禁止缓存,请求和响应不得存储在缓存目录中。onlyIfCached():仅从本地缓存获取数据。若缓存不存在或已过期,直接返回504 Unsatisfiable Request。minFresh(seconds, unit):指定缓存至少还要保持新鲜的秒数,如果预期即将过期则不使用。maxStale(seconds, unit):允许返回已经过期的缓存,只要过期时间不超过指定的秒数。
// 请求级覆盖:强制使用网络
Request networkRequest = new Request.Builder()
.url("https://api.example.com/data")
.cacheControl(CacheControl.FORCE_NETWORK)
.build();
// 请求级覆盖:强制使用缓存(如果没有缓存则返回504)
Request cacheOnlyRequest = new Request.Builder()
.url("https://api.example.com/data")
.cacheControl(CacheControl.FORCE_CACHE)
.build();
// 自定义:允许接受最多1分钟过期的缓存
Request staleRequest = new Request.Builder()
.url("https://api.example.com/data")
.cacheControl(new CacheControl.Builder()
.maxStale(1, TimeUnit.MINUTES)
.build())
.build();
优先级规则:服务器返回的
Cache-Control 指令优先级高于客户端请求指令。例如服务器指定 no-store 时,客户端的 max-age 请求指令将无效。
2.3 CacheStrategy 决策树内部机制
CacheStrategy 是 OkHttp 缓存策略的核心协调器。它根据当前请求的 CacheControl 指令、缓存响应的 CacheControl 指令以及缓存的年龄,对三个结果进行决策:返回缓存、发起网络请求、返回 504 错误。下方流程图描绘了这一决策树逻辑的完整路径。
源码核心逻辑解读: OkHttp 的
CacheStrategy 工厂通过判断 strategy.networkRequest 和 strategy.cacheResponse 是否为 null 来路由决策。若 networkRequest 为 null 则强制缓存;若 cacheResponse 为 null 则返回 504;两者均不为 null 则进入协商缓存流程。