实战:为 Retrofit 添加基础缓存

好,咱们直接动手。说到缓存,我习惯在项目初期就把它加进去 —— 因为后期再补,往往要改不少代码。Retrofit 本身不直接管缓存,全靠底层的 OkHttp。所以给 Retrofit 加缓存,本质上就是给 OkHttp 配个 Cache 对象,再设置合理的拦截策略。

1. 缓存到底放在哪?

先想一个问题:缓存在哪存?说白了,就是本地磁盘上的一个目录。OkHttp 会在这个目录下维护一个 journal 文件,以及一堆缓存实体文件。我见过有人直接把缓存丢到 /sdcard 上 — 嗯,那权限问题够你喝一壶的。正确的路径应该是 context.cacheDir 或者 context.externalCacheDir。

关键点: 如果应用被用户清除缓存,internalCacheDir 也会被清掉,这符合用户预期。

2. 缓存需要多大?

别随便写个 10MB 就完事。我通常在项目里根据实际图片和 JSON 大小来算:

  • 纯 API 响应(JSON):一个接口响应平均 2-5KB,缓存 500 个响应大概 2.5MB
  • 如果有小图(缩略图):每个 50-100KB,缓存 100 张 ≈ 10MB
  • 总缓存大小建议取两者的和再乘 1.5,留点余量

我个人习惯设 20MB,对大多数 App 够用,还不占太多空间。

3. 核心代码:三步搞定基础缓存

下面这段代码,你在项目里直接拷贝就能用。不过别只复制 — 看懂每一步的作用才是关键。

// 1. 创建缓存目录
File cacheDir = File(context.cacheDir, "http_cache")
val cacheSize = 20L * 1024 * 1024 // 20MB

// 2. 构建带缓存的 OkHttpClient
val cache = Cache(cacheDir, cacheSize)

val okHttpClient = OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor { chain ->
        val request = chain.request()
        // 这里可以设置统一的缓存策略
        val response = chain.proceed(request)
        response.newBuilder()
            .header("Cache-Control", "public, max-age=60") // 缓存 60 秒
            .build()
    }
    .build()

// 3. 构建 Retrofit
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttpClient)  // 注入 OkHttpClient
    .addConverterFactory(GsonConverterFactory.create())
    .build()

注意看 addInterceptor 那段。我在项目中遇到过一个坑:如果不显式添加 Cache-Control 头,服务端返回的缓存策略可能会被客户端忽略。说白了,客户端和服务端得“商量好”缓存规则。

4. 缓存策略到底怎么设?

你想想看,如果一个接口的数据一天内不会变,你给它设个 1 分钟缓存,那跟没设一样。我们得区分场景:

场景 推荐缓存策略 说明
用户信息 / 配置 max-age=300 5 分钟刷新一次,基本够用
新闻列表 / 推荐流 max-age=60 1 分钟,用户来回切也能看旧数据
商品详情页 max-age=0, must-revalidate 每次都校验,但可以走条件请求(304)
图片资源 max-age=86400 1 天,很少变动
直播预告等实时信息 no-cache 不缓存,但可以存 ETag

说实话,大部分团队一开始都只设个统一的 max-age。我建议尽早拆分策略,否则缓存命中率上不去 — 你跟老板解释“为什么刷新了还是旧数据”时会很头疼。

5. 避坑指南

⚠️ 注意: 缓存目录必须在 OkHttpClient 构建前创建好。如果你在子线程创建目录,而主线程已经在构建客户端了,会导致 FileNotFoundException。我曾经因为这个 bug 查了一下午。
💡 小技巧: 调试阶段可以在 Android Studio 的 Device File Explorer 里查看 /data/data/包名/cache/http_cache/ 目录下的 journal 文件。里面记录了每个请求的缓存 key、大小和状态,是排查缓存不命中的利器。

6. 怎么验证缓存命中率?

加完缓存,心里没底?我一般用两种方式:

  • 日志法: 在拦截器里打印 response.cacheResponse() 和 response.networkResponse()。如果 cacheResponse 不为 null,说明命中了缓存。
  • 抓包法: 用 Charles 或 Stetho 看请求是否真的出去了。没发网络请求,就是缓存生效了。

下面这张图把整个配置流程串了起来,你可以对照着梳理自己的代码。

Retrofit 基础缓存配置流程 ① 创建缓存目录 ② 指定缓存大小 ③ 构建 Cache 对象 ④ 添加到 OkHttpClient.Builder ⑤ 设置拦截器及缓存策略 ⑥ 构建 Retrofit 实例 注意事项 · 目录需在构建前创建 · 缓存大小要结合实际 · 建议按接口分策略

这份基础配置跑起来后,你就能在日志里看到缓存命中的打印。后面我们再深入聊怎么针对不同接口精细调节策略、怎么处理离线缓存、以及怎么统计命中率曲线。

现在就可以做: 在 app/build.gradle 中加入 implementation("com.squareup.okhttp3:okhttp:4.11.0") 依赖,然后照着上面的代码配一遍。你会看到第二次请求明显变快。