4、开启OkHttp缓存:配置Cache对象(目录、大小)
缓存这玩意儿,说白了一个字——省。省流量、省时间、省服务器压力。我在做一款新闻资讯App时,发现每次下拉刷新都要重新请求图片和JSON,用户体验卡得要命。后来加了缓存,页面秒开,连后台都跑来问「你是不是偷偷做了预加载?」
OkHttp的缓存机制不复杂,核心就两步:造一个Cache对象,然后塞给OkHttpClient。但这里面的坑,嗯,比想象中多。咱们一个一个来。
4.1 Cache对象:目录与大小
Cache构造函数只有两个参数:
Cache(File directory, long maxSize);
directory:缓存文件存放的目录。你必须确保这个目录存在,而且App有读写权限。
maxSize:缓存的最大字节数。超过这个值,OkHttp会按照LRU策略自动清理旧缓存。
我个人习惯把缓存目录放在 context.getCacheDir() 下,比如:
File cacheDir = new File(context.getCacheDir(), "okhttp_cache");
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024); // 10MB
⚠️ 我曾经踩过的坑:
有一次直接用了
有一次直接用了
getExternalCacheDir(),结果用户一插USB传输文件,缓存路径就变了,App直接崩溃。所以对内存储路径,我建议统一用 getCacheDir()——它不会随SD卡挂载状态变化,安全又稳妥。
4.2 缓存大小怎么选?
这个问题没有标准答案,但我有几个经验值供你参考:
| 场景 | 推荐缓存大小 | 原因 |
|---|---|---|
| 新闻/图文类App | 10MB – 50MB | 图片+文章JSON,一天量大概5-15MB |
| 视频类App(缩略图) | 20MB – 100MB | 封面图多,但视频本身不缓存 |
| 工具类/API查询 | 1MB – 5MB | 只缓存接口响应,数据量小 |
| 低端机/存储敏感 | 不超过 getCacheQuota() |
避免撑爆用户存储 |
💡 小技巧:
你可以用
你可以用
StorageStatsManager 动态获取可用空间,然后按比例分配。比如取剩余空间的2%作为缓存上限,这样既不会浪费也不会爆。
4.3 为OkHttpClient设置缓存
拿到Cache对象后,直接在Builder上调用 cache() 即可:
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
就这么简单?对。但真正让缓存「动起来」的是HTTP响应头里的 Cache-Control。OkHttp只负责存储,怎么缓存、缓存多久,那是服务器和你自己决定的。关于Cache-Control的详细配置,咱们下一章再展开,这里先做个铺垫。
📌 关键点回顾:
- 目录必须保证存在且有写权限。
- 大小建议根据业务动态调整,不用死磕固定值。
- 缓存只存储符合缓存条件的响应(即服务器返回了Cache-Control或Expires等)。
- 目录必须保证存在且有写权限。
- 大小建议根据业务动态调整,不用死磕固定值。
- 缓存只存储符合缓存条件的响应(即服务器返回了Cache-Control或Expires等)。
4.4 验证缓存是否生效
配置完心里没底?我一般会抓包看两个地方:
- 第一次请求:返回200,响应头带
Cache-Control: public, max-age=3600 - 第二次请求(一小时之内):返回
200 (disk cache),或者状态码304 Not Modified
你可以在OkHttp的日志拦截器里打印缓存命中情况,或者直接看响应头的 Age、X-Cache 字段。没有日志?没关系,可以加一个自定义拦截器手动打印:
class CacheLogInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
Response.CacheControl cacheControl = response.cacheControl();
Log.d("OkHttp", "URL: " + request.url()
+ " Cache: " + (response.cacheResponse() != null ? "HIT" : "MISS")
+ " Age: " + response.header("Age"));
return response;
}
}
你想想看,有了这个日志,谁还敢说缓存没生效?
4.5 整个配置流程总览
下面这张图把从目录创建到缓存生效的完整路径串了起来,建议你保存下来,以后配置时拿出来看一眼:
4.6 善后与监控
缓存配置好了,别忘了偶尔看看它是不是正常工作。我习惯在App的开发者菜单里加一个「清除缓存」的按钮,方便测试:
// 清理整个缓存目录
client.cache().evictAll();
或者只想清理某条URL的缓存:
// 需要知道完整的请求对象
Request request = new Request.Builder().url("https://api.example.com/data").build();
cache.remove(request);
💡 监控指标:
你可以通过
你可以通过
cache.flush() 后的文件列表,或者 cache.size() 来获取当前占用空间。如果发现缓存增长异常(比如文件数量暴涨),多半是某些响应没有Cache-Control导致每次都生成新文件——这时候该检查服务端的配置了。