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 协商缓存头。当同时出现时遵循以下优先级规则:

  1. Cache-Control > Expires:Response 中同时存在 Cache-Control: max-age=3600 和 Expires: Sun, 01 Jan 2023 00:00:00 GMT,则以 max-age 为准(Expires 被忽略)。
  2. ETag > Last-Modified:条件请求中同时提供 If-None-Match(对应 ETag)和 If-Modified-Since(对应 Last-Modified)时,服务器优先校验 ETag,只有 ETag 一致且 Last-Modified 满足时才返回 304。
  3. Expires 为时间,依赖客户端时钟;Cache-Control max-age 为相对时间,不受时钟偏差影响,推荐优先使用。

常见错误:混合使用 Cache-Control 与 Expires 但值不一致,会导致缓存策略混乱。2012 年后所有主流浏览器均已将 Expires 视为后备方案,建议服务端移除 Expires 头,仅保留 Cache-Control。

1.4 缓存决策流程图(强缓存 → 协商缓存 → 服务器响应)

HTTP 缓存决策流程:强缓存 → 协商缓存 → 服务器 ① 客户端发起请求 ② 查询本地缓存 未过期 强缓存命中
200 (from cache) 已过期 ③ 发送条件请求 If-None-Match / If-Modified-Since ④ 服务器比较 ETag/Last-Modified 未变 304 Not Modified 使用本地缓存 body 已变 ⑤ 200 OK(全量响应)

上图展示了从客户端请求到服务器响应的完整缓存决策路径:从本地缓存(强缓存)开始,若未过期则直接返回;若过期则进入协商缓存阶段,携带条件头向服务器验证;服务器根据 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 数据。