OkHttp 缓存原理概述
大家好,我是你们的老朋友。今天我们来聊聊 OkHttp 的缓存模块。说实话,缓存这块我以前一直觉得很简单,直到在生产环境踩了几次坑,才真正重视起来。
OkHttp 内置的缓存模块,说白了就是一套“请求-存储-校验”的机制。它帮你把网络返回的内容存起来,下次请求同样的资源时,直接用本地副本,避免重复下载。背后有两个核心类撑腰:Cache 和 CacheStrategy。
Cache 类——缓存的“仓库管理员”
Cache 负责管理磁盘上的缓存数据。你可以把它想象成一个带索引的文件柜:
- 写入:把响应存入指定目录,按 URL 和请求参数计算键值。
- 读取:根据键值查找本地文件,返回缓存的响应。
- 清理:当总大小超过上限时,按 LRU(最近最少使用)策略删除旧条目。
我早期项目里曾经犯过一个错:给 Cache 设了个 50MB 的上限,结果图片列表页面缓存不到 100 张就满了。后来才明白,要根据业务数据量合理调大。嗯,这地方要留心。
CacheStrategy——缓存的“决策大脑”
CacheStrategy 才是真正的灵魂。它根据请求头、响应头中的缓存控制指令(比如 Cache-Control、Expires、ETag、Last-Modified),决定一个请求到底该用缓存、走网络、还是先校验再决定。
它的决策结果有三种:
- 完全使用缓存:缓存未过期,直接返回本地副本,零网络开销。
- 走网络:没有缓存或缓存已过期且需要强制刷新。
- 条件请求:缓存可能过期,但带上
If-None-Match或If-Modified-Since去服务端询问。返回 304 则复用缓存,返回 200 则更新缓存。
我个人习惯把 CacheStrategy 比作“红绿灯系统”——它会根据路况(也就是缓存头)决定你该停(用缓存)、开(走网络)、还是闪黄灯(条件请求)。
工作流程——一张图说清
我把整个流程画成了 SVG,你一看就明白:
关键表格:三种缓存策略对比
| 策略类型 | 触发条件 | 网络请求 | 适用场景 |
|---|---|---|---|
| 完全使用缓存 | Cache-Control: max-age 未过期,且无强制刷新 | 无 | 静态资源(JS、CSS、图片) |
| 条件请求 | 缓存已过期但存在 ETag/Last-Modified | 一次 HEAD 或条件 GET | 经常变化但可用旧版本替代的场景 |
| 走网络 | 无缓存 / 缓存过期且无校验头 / 强制 no-cache | 完整的 GET 请求 | 实时数据、API 接口 |
📌 核心要点:OkHttp 的缓存策略完全遵循 HTTP 标准。你不需要自己写缓存逻辑,只需要在服务端和客户端正确配置
Cache-Control 头。我见过很多团队在客户端硬编码缓存时长,结果跟服务端策略打架——一定不要这么做。
配置示例——几行代码搞定
我们先看如何创建 Cache 实例,然后把它挂到 OkHttpClient 上:
// 设置缓存目录,应用内部存储
File cacheDir = new File(context.getCacheDir(), "http_cache");
// 最大缓存容量,我建议根据业务调整,我通常设 20~50MB
long maxSize = 50 * 1024 * 1024; // 50MB
Cache cache = new Cache(cacheDir, maxSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
然后发起一个普通请求,OkHttp 会自动处理缓存流程:
Request request = new Request.Builder()
.url("https://api.example.com/user/profile")
.build();
// 第一次请求,肯定走网络
Response response = client.newCall(request).execute();
// 响应会被自动缓存
// 第二次(在过期前),直接从缓存返回
Response cachedResponse = client.newCall(request).execute();
// cachedResponse.cacheResponse() 不为 null 表示来自缓存
💡 小技巧:如果你想强制走网络,可以在请求上加
.cacheControl(CacheControl.FORCE_NETWORK)。反之,强制使用缓存可以用 FORCE_CACHE。我在做离线模式时经常用这招。
避坑指南——我曾经踩过的坑
嗯,这里我要多说几句。缓存虽好,但配置不当就是灾难:
- 缓存目录被清空:我早期遇到过一个 bug——用户清除应用数据后缓存目录没了,但 OkHttp 不会自动重建。后来我在 Application 初始化时手动创建目录,避免首次写入时抛异常。建议加上
cacheDir.mkdirs()。 - 响应体过大导致 OOM:如果一个响应有几十 MB,再大的缓存也扛不住。我建议在服务端对大数据接口禁用缓存,或者设置
CacheControl.noStore()。 - 缓存和 Cookie 的冲突:某些接口依赖 Cookie 做鉴权,但缓存会无视 Cookie。我记得有一次排查很久,才发现缓存的响应里没有携带最新的 Cookie,导致后续请求 401。解决方案是对于需要鉴权的接口,加上
CacheControl.noCache()或者自定义拦截器。
⚠️ 警告:永远不要在纯测试环境以外使用
FORCE_CACHE 作为默认策略。一旦服务端更新了数据,用户会一直看到旧内容。你想想,用户骂你的时候可不会管你是不是开了缓存。