HTTP 缓存机制回顾:强缓存与协商缓存
HTTP 缓存是网络性能优化的第一道防线,通过复用已获取的响应减少网络请求量。在 OkHttp 与 Retrofit 的集成实践中,理解底层缓存协议是合理配置拦截器、设计离线策略的前提。本章聚焦两个核心概念:强缓存(无需与服务器交互)与协商缓存(需要发送条件请求),并拆解其对应的指令语义与优先级规则。
1.1 强缓存 vs 协商缓存:一次性决策表
| 对比维度 | 强缓存 | 协商缓存 |
|---|---|---|
| 是否发请求 | 不发送请求,直接从本地缓存读取 | 发送请求,携带条件头(If-Modified-Since / If-None-Match) |
| 状态码 | 200 (from disk/memory cache) | 304 Not Modified(响应无 body) |
| 缓存失效触发 | Cache-Control: max-age 到期 / Expires 过期 | ETag 变化 / Last-Modified 后资源有更新 |
| 性能收益 | 零网络延迟,最佳 | 一次小型条件请求,节省完整响应传输 |
| 适用场景 | 静态资源(图片、字体、JS/CSS 版本化文件) | HTML、API 数据、需要实时验证的资源 |
关键结论:强缓存命中时完全绕过网络;协商缓存命中时只传输 header(约 200~500 字节),不传输 body。实际应用中二者往往混合使用。
1.2 Cache-Control 指令语义(核心参数)
Cache-Control 是 HTTP/1.1 定义的缓存控制首部,其指令优先级最高,应作为现代应用程序的默认选择。以下为 OkHttp 集成中最常操作的指令:
| 指令 | 类型 | 说明 |
|---|---|---|
max-age=<秒数> |
响应/请求 | 资源被缓存的最大秒数,到期前强缓存有效 |
s-maxage=<秒数> |
响应 | 共享缓存(如 CDN)专用 max-age,覆盖 max-age |
no-cache |
响应/请求 | 强制每次使用前向服务器验证(协商缓存) |
no-store |
响应 | 禁止缓存任何响应内容(敏感数据) |
public / private |
响应 | public:任何缓存皆可存储;private:仅用户私有缓存可存储 |
must-revalidate |
响应 | 过期后必须后端验证,不可直接使用过期缓存 |
immutable |
响应 | 资源不会变化(如图片 hash 版本),浏览器可跳过条件请求 |
OkHttp 实践提示:通过 CacheControl.Builder 可动态构造请求或响应策略,例如强制刷新时使用 CacheControl.FORCE_NETWORK,离线兜底时使用 CacheControl.FORCE_CACHE。
1.3 Expires 与 Last-Modified 的优先级对比
Expires 是 HTTP/1.0 头部,Last-Modified 是 HTTP/1.1 协商缓存头。当同时出现时遵循以下优先级规则:
- Cache-Control > Expires:Response 中同时存在
Cache-Control: max-age=3600和Expires: Sun, 01 Jan 2023 00:00:00 GMT,则以 max-age 为准(Expires 被忽略)。 - ETag > Last-Modified:条件请求中同时提供
If-None-Match(对应 ETag)和If-Modified-Since(对应 Last-Modified)时,服务器优先校验 ETag,只有 ETag 一致且 Last-Modified 满足时才返回 304。 - Expires 为时间,依赖客户端时钟;Cache-Control max-age 为相对时间,不受时钟偏差影响,推荐优先使用。
常见错误:混合使用 Cache-Control 与 Expires 但值不一致,会导致缓存策略混乱。2012 年后所有主流浏览器均已将 Expires 视为后备方案,建议服务端移除 Expires 头,仅保留 Cache-Control。
1.4 缓存决策流程图(强缓存 → 协商缓存 → 服务器响应)
上图展示了从客户端请求到服务器响应的完整缓存决策路径:从本地缓存(强缓存)开始,若未过期则直接返回;若过期则进入协商缓存阶段,携带条件头向服务器验证;服务器根据 ETag/Last-Modified 判断返回 304(复用缓存)或 200(更新缓存)。
1.5 关键指令优先级汇总
| 优先级 | 头部/指令 | 作用 |
|---|---|---|
| 1(最高) | Cache-Control(如 max-age) |
控制强缓存有效期(相对时间) |
| 2 | Pragma: no-cache(HTTP/1.0 向后兼容) |
等效于 Cache-Control: no-cache,优先级低于 Cache-Control |
| 3 | Expires |
时间过期,仅当无 Cache-Control 时生效 |
| 4 | ETag / Last-Modified |
协商缓存标识,ETag 优先级通常高于 Last-Modified |
OkHttp 集成准备:在下一章我们将看到如何通过 OkHttp 的 Cache 类初始化本地缓存目录,并使用拦截器读取这些 HTTP 头部来实现自动缓存管理。
1.6 强缓存与协商缓存典型响应示例
以下为一个使用 Cache-Control 和 ETag 的典型服务器响应头,以及对应的客户端后续请求:
# 第一次请求响应(200 OK)
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=3600
ETag: W/"abc123def"
Last-Modified: Wed, 21 Jun 2023 12:00:00 GMT
Content-Length: 512
{"data": "some data"}
# 3600秒后,客户端发起条件请求(GET /api/data)
GET /api/data HTTP/1.1
Host: example.com
If-None-Match: W/"abc123def"
If-Modified-Since: Wed, 21 Jun 2023 12:00:00 GMT
# 服务器返回 304(资源未变)
HTTP/1.1 304 Not Modified
ETag: W/"abc123def"
Cache-Control: public, max-age=3600
注意 304 响应体为空,客户端直接复用上次缓存的 JSON 数据。