OkHttp响应缓存教程:RFC 9111标准缓存、条件缓存命中如何帮你省流量
2026/9/3 12:06:05 网站建设 项目流程

OkHttp响应缓存教程:RFC 9111标准缓存、条件缓存命中如何帮你省流量

【免费下载链接】okhttpA meticulous HTTP client for the JVM, Android, and GraalVM.项目地址: https://gitcode.com/gh_mirrors/okh/okhttp

OkHttp 是面向 JVM、Android 和 GraalVM 的 HTTP 客户端,它的响应缓存严格遵循 RFC 9111 标准,能把重复请求直接由本地缓存命中,彻底省掉一次网络往返。本教程带你搞懂:如何开启 OkHttp 缓存、三种缓存命中场景的区别,以及条件缓存(ETag / 304)为什么是省流量的关键。

如上图所示,OkHttp 的每一个请求都会先经过核心的缓存判断(CACHE),再决定是否真正走网络(NETWORK)——这就是省流量的第一步。

为什么要用 OkHttp 响应缓存?

没有缓存时,每次请求都要经历 DNS 解析、建连、TLS 握手、下载响应体,耗时会高达几百毫秒。启用缓存后:

  • 缓存命中(Cache Hit):完全不访问网络,响应速度从几百毫秒降到几毫秒,流量消耗为 0;
  • 条件命中(Conditional Hit):只传一个小小的校验头,服务器返回304 Not Modified时响应体为 0 字节,流量几乎为零;
  • 离线可用:弱网或无网场景下,only-if-cached模式可直接读取本地副本。

OkHttp 的缓存默认关闭,需要显式启用,且目标是"RFC 正确 + 务实"的行为,遇到规范歧义时与 Chrome/Firefox 等现代浏览器保持一致。

一键开启缓存:最简单配置方法

只需要在构建OkHttpClient时通过cache()指定一个目录和大小上限。以下 Android 示例设置 50 MiB(文档原注释:2020 年值约 5 美分的手机存储空间):

val client = OkHttpClient.Builder() .cache(Cache( directory = File(application.cacheDir, "http_cache"), maxSize = 50L * 1024L * 1024L // 50 MiB )) .build()

两个要点:

  1. 缓存目录必须被单个 Cache 实例独占,但同一个 Cache 可被多个OkHttpClient共享;
  2. 缓存设计用于跨应用重启持久化,无需每次启动都清空。

相关源码:Cache.kt,官方缓存文档见 docs/features/caching.md。

三种命中场景:Hit、Miss 与 Conditional Hit

OkHttp 通过EventListener暴露缓存事件,三种典型场景如下(完整事件流见 docs/features/events.md):

场景事件序列是否走网络说明
Cache HitCallStart →CacheHit→ CallEnd理想情况,直接跳过 DNS、建连、下载
Cache MissCallStart →CacheMiss→ 常规网络事件首次请求、不可缓存或已过期
Conditional HitCallStart →CacheConditionalHit→ 网络事件 → 响应体 0 字节 →CacheHit是(极小)服务器确认未修改,返回 304

上图展示了正常一次调用的事件顺序;当缓存命中时,绿色"建连"区域和紫色"响应体下载"部分都会被大幅跳过,这正是省时间的来源。

新鲜度怎么算?(RFC 9111 的新鲜性规则)

判断"还能不能直接用缓存"的逻辑在 CacheStrategy.kt 中,核心规则:

  1. 响应带Cache-Control: max-age→ 按该时长计算新鲜期;
  2. 否则带Expires头 → 按到期时间计算;
  3. 否则带Last-Modified头 → 采用 RFC 建议的启发式新鲜期:文档年龄(响应时间减去最后修改时间)的10%
  4. 带 query 参数的 URL 不使用默认过期时间(这类 URL 通常指向动态内容)。

条件缓存命中:ETag 如何帮你省流量

这是 OkHttp 响应缓存最精髓的部分。当缓存副本过期(stale)但携带ETagLast-Modified时,OkHttp 不会傻乎乎地全量重新下载,而是发起条件请求

  • ETag→ 请求头加上If-None-Match: <etag>(优先);
  • 否则有Last-Modified→ 加上If-Modified-Since: <date>

服务器对比后发现资源没变,就返回304 Not Modified不携带响应体——OkHttp 此时把本地缓存副本作为最终响应返回。一次几 MB 的文件更新检查,流量可能只有几十字节的请求头。响应中cacheResponsenetworkResponse均非空,且仅当状态码为 304 时缓存副本才作为顶层响应。

源码实现见 CacheStrategy.kt 中"选择校验条件"的分支逻辑。

💡 想让缓存效率最大化?在服务器端给静态资源返回ETag和合理的max-age是最有效的手段。

三种常用 CacheControl 指令

CacheControl.kt 提供了三类常用指令,配合刷新/离线功能非常好用:

指令用途
noCache强制走网络全量刷新(用户点击"刷新"按钮时使用)
maxAge(0)只要求服务器校验缓存,比 noCache 更省流量
onlyIfCached只读本地缓存,无缓存则直接返回 504,适合离线模式

缓存管理与命中率统计

Cache实例跟踪三个关键指标,帮你量化缓存效果:

  • requestCount:累计请求数;
  • networkCount:走了网络的请求数;
  • hitCount:由缓存满足的请求数。

注意:条件命中会同时计入 networkCount 和 hitCount——因为既有网络交互也有缓存命中。日常运维操作:

  • cache.evictAll():清空全部缓存释放空间;
  • 遍历cache.urls()可按 URL 前缀精确删除(如下拉刷新后只清某个域名);
  • cache.delete():彻底删除目录,一般只在卸载时调用。

常见坑:为什么缓存没生效?

最常见的原因:响应体没有读完。只有被完整读取、取消或停滞的响应才会写入缓存,请确保try-with-resources关闭了 Response。其他注意点:

  • 请求或响应任一端的no-store指令都会禁止缓存;
  • 3xx 重定向类响应需要Expiresmax-agepublic/private头才允许缓存;
  • 如果请求自己带了If-None-Match等条件头,内置缓存将不参与,由你的代码自行处理。

更多细节见官方缓存文档 docs/features/caching.md 的 Troubleshooting 章节,规范遵循情况见 README.md 中对 RFC 9110 / 9111 / 9112 的说明。

小结

  • OkHttp 响应缓存遵循RFC 9111,默认关闭,cache(Cache(目录, 大小))一行开启;
  • 新鲜期内直接命中省掉全部网络;过期后条件命中(ETag + 304)让响应体归零;
  • 善用maxAge(0)/onlyIfCached平衡刷新与离线体验;
  • 记住"响应要读完才入库",并用 hitCount 验证缓存收益。

掌握这套机制后,你的应用可以在不牺牲数据实时性的前提下,显著降低流量消耗与首屏延迟。

【免费下载链接】okhttpA meticulous HTTP client for the JVM, Android, and GraalVM.项目地址: https://gitcode.com/gh_mirrors/okh/okhttp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询