3. OkHttp 默认缓存配置

3.1 缓存机制概述

OkHttp 的缓存层基于 磁盘缓存(DiskLruCache) 实现,以 URL 为缓存键,结合响应头中的 Cache-Control、Expires、ETag、Last-Modified 等字段判断缓存的有效性。默认情况下缓存是关闭的,必须显式创建 Cache 对象并关联到 OkHttpClient 才能启用。

缓存配置涉及三个核心要素:

  • 缓存目录 — 存储缓存文件的 File 对象
  • 最大大小 — 缓存占用磁盘空间的硬上限(字节)
  • Cache 对象 — 封装目录与大小,提供读写接口
OkHttp 默认缓存配置流程 缓存目录 (File) 最大大小 (maxSize) Cache 对象 new Cache(dir, maxSize) Builder .cache(cache) OkHttpClient (已启用缓存) 网络请求 与响应 配置核心:Cache 对象同时需要目录和最大大小,两者缺一不可;关联到 Builder 后全局生效

3.2 配置核心步骤

启用 OkHttp 缓存只需以下 4 步:

  1. 创建缓存目录 — 在应用的内部存储中分配一个专用目录
  2. 设定最大大小 — 根据业务数据量估算合理的磁盘占用上限
  3. 构造 Cache 对象 — 将目录和大小封装为 Cache 实例
  4. 关联 OkHttpClient — 通过 Builder.cache() 注入,使全局请求共享缓存

关键约束:Cache 对象是线程安全的,建议整个应用只创建一个 OkHttpClient 实例并复用,避免多次初始化缓存目录导致状态不一致。

3.3 核心 API 详解

参数 / API类型说明建议值
cacheDirFile缓存文件存放的目录,必须可读写context.getCacheDir() + "/okhttp"
maxSizelong缓存最大字节数,超出后按 LRU 淘汰10MB ~ 100MB(视业务而定)
new Cache(dir, maxSize)Cache构造缓存实例,初始化 journal 文件—
Builder.cache(cache)OkHttpClient.Builder将缓存注入客户端构建器非 null 即可
client.cache()Cache返回当前客户端关联的缓存对象(调试用)—

完整配置代码(Android 环境):

// 1. 创建缓存目录
File cacheDir = new File(context.getCacheDir(), "okhttp_cache");
if (!cacheDir.exists()) {
    cacheDir.mkdirs();
}

// 2. 设定最大大小:10 MB
long maxSize = 10 * 1024 * 1024;

// 3. 构造 Cache 对象
Cache cache = new Cache(cacheDir, maxSize);

// 4. 关联到 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .build();

// 验证:打印缓存目录与大小
System.out.println("Cache directory: " + client.cache().directory());
System.out.println("Cache max size: " + client.cache().maxSize());

纯 Java 服务端配置(无 Android Context):

File cacheDir = new File("/tmp/okhttp-cache");
long maxSize = 50 * 1024 * 1024;  // 50 MB
Cache cache = new Cache(cacheDir, maxSize);

OkHttpClient client = new OkHttpClient.Builder()
        .cache(cache)
        .build();

3.4 调试与验证

缓存是否生效可以通过响应头或 OkHttp 提供的 缓存状态标记 来判断:

方式一:检查响应头

Request request = new Request.Builder()
        .url("https://api.example.com/data")
        .build();

Response response = client.newCall(request).execute();

// 打印缓存相关头
System.out.println("Cache-Control: " + response.header("Cache-Control"));
System.out.println("Date: " + response.header("Date"));
System.out.println("Expires: " + response.header("Expires"));
System.out.println("ETag: " + response.header("ETag"));

// 关键:判断缓存来源
if (response.cacheResponse() != null) {
    System.out.println("[CACHE HIT] 响应来自本地缓存");
} else {
    System.out.println("[CACHE MISS] 响应来自网络");
}

if (response.networkResponse() != null) {
    System.out.println("网络状态码: " + response.networkResponse().code());
}

response.close();

方式二:通过 Cache 统计信息

Cache cache = client.cache();
System.out.println("请求总数: " + cache.requestCount());
System.out.println("网络请求数: " + cache.networkCount());
System.out.println("缓存命中数: " + cache.hitCount());
System.out.println("缓存命中率: " + (cache.hitCount() * 100.0 / cache.requestCount()) + "%");

调试技巧:首次请求必定 MISS,第二次相同 URL 请求若响应头允许缓存则会 HIT。可以在 response.cacheResponse() 不为 null 时确认缓存生效。

3.5 常见错误与避坑

  • 缓存目录不可写 — 使用外部存储时可能没有写入权限,导致 Cache 构造失败或写入异常。始终优先使用 context.getCacheDir()。
  • maxSize 设置过小 — 缓存频繁被淘汰,导致命中率极低。建议根据单次响应平均大小 × 期望缓存条目数 × 1.5 安全系数来估算。
  • 多实例共享同一目录 — 多个 OkHttpClient 指向同一个缓存目录会造成 journal 文件冲突,抛出 IOException。确保全局唯一客户端或唯一目录。
  • 未配置缓存即调用 cache() — client.cache() 返回 null,在未判空时调用方法引发 NPE。使用前始终判空。
  • 忽略响应头约束 — 服务器返回 Cache-Control: no-store 或 private 时,即使配置了缓存也不会存储。需后端配合。

典型异常日志:java.io.IOException: journal is not writable 或 java.io.FileNotFoundException: /okhttp_cache/journal (Permission denied)。这两个错误均与缓存目录权限或多进程冲突有关。

3.6 性能与稳定性优化

优化方向具体措施预期收益
缓存大小根据业务数据的日增量设置 maxSize,预留 50% 缓冲区减少 LRU 淘汰频率,提升命中率
目录选择Android 使用 context.getCacheDir();服务端使用独立临时目录避免权限问题,系统可自动清理
客户端复用使用单例或依赖注入维护唯一的 OkHttpClient 实例避免重复创建 Cache,降低 journal 损坏风险
监控告警定期采集 cache.hitCount() 和 cache.networkCount()及时发现缓存命中率异常,辅助调优
缓存预热应用启动时对关键 API 发起一次请求让高频数据提前进入缓存,减少首屏网络耗时

稳定性建议:

  • 在 Application 初始化阶段完成缓存配置,避免运行时动态切换。
  • 对 Cache 构造过程添加 try-catch,降级为无缓存模式:
Cache cache = null;
try {
    cache = new Cache(cacheDir, maxSize);
} catch (IOException e) {
    // 降级:不使用缓存
    System.err.println("缓存初始化失败,降级为无缓存模式: " + e.getMessage());
}

OkHttpClient.Builder builder = new OkHttpClient.Builder();
if (cache != null) {
    builder.cache(cache);
}
OkHttpClient client = builder.build();

最佳实践:在 Android 中,系统可能在低存储空间时自动清除 getCacheDir() 下的文件,应用无需手动管理。但建议监控 cache.size() 与 maxSize 的比例,超过 90% 时考虑动态调整上限。