3. OkHttp Cache 对象配置:磁盘缓存路径、大小、创建与初始化代码

OkHttp 的 Cache 对象是所有缓存策略的物理载体。它管理磁盘上缓存的 HTTP 响应,决定哪些请求可以复用本地副本。正确配置缓存路径与大小,并在 OkHttpClient 创建时正确注入,是离线拦截功能稳定的基础。

3.1 磁盘缓存路径的选择

缓存目录必须是应用可读写的私有目录,避免被其他进程干扰或系统清理。常见选项包括:

路径类型推荐指数说明典型值
应用内部缓存目录★★★★★context.cacheDir,系统可清理,多用户隔离/data/data/包名/cache/okhttp/
应用外部缓存目录★★★★☆context.externalCacheDir,SDK 可用但可能存在无权限/storage/emulated/0/Android/data/包名/cache/okhttp/
应用私有文件目录★★★☆☆context.filesDir,系统不自动清理,需人工管理/data/data/包名/files/okhttp_cache/
临时系统路径★☆☆☆☆System.getProperty("java.io.tmpdir"),可能不可写/tmp/ (Android 上不可用)
警告: 禁止使用 SD 卡根目录或外部公有目录,会被其他应用读取,且系统可能随时删除。Android 10+ 对 externalCacheDir 也有访问限制,务必使用 context.cacheDir。

3.2 缓存大小的确定

缓存大小直接影响命中率与磁盘压力。OkHttp 使用固定的“最大容量”策略,超过上限时自动淘汰最旧的条目。

  • 推荐值: 网络密集型应用 20MB~50MB,轻量应用 5MB~10MB。
  • 计算依据: 平均响应大小 × 期望缓存条目数。例如平均 50KB、保留 400 条 ≈ 20MB。
  • 动态计算: 可根据可用存储空间按比例设置,例如 min(50MB, deviceFreeSpace × 0.01)。
  • 注意: 设置过小导致频繁淘汰,过大可能触发系统清理。最大不超过 100MB(视应用场景)。
实践建议: 在应用首次启动时,通过 apkDataSize 或用户等级来动态调整。在调试阶段可以设置一个较大的值(如 100MB),上线前按真实流量压测。

3.3 创建与初始化 Cache 对象

OkHttp 的 Cache 构造函数接受一个 File 对象和一个 long 字节数。推荐做法是先确保目录存在,再构造实例。

3.3.1 基础代码模板

// 定义缓存目录:在应用内部缓存下创建 okhttp 子目录
File cacheDir = new File(context.cacheDir, "okhttp");
if (!cacheDir.exists()) {
    cacheDir.mkdirs();
}
// 缓存大小:20MB
long cacheSize = 20L * 1024L * 1024L;
// 创建 Cache 对象
Cache cache = new Cache(cacheDir, cacheSize);

3.3.2 集成到 OkHttpClient

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

3.3.3 带异常处理的完整初始化

public Cache createOkHttpCache(Context context) {
    File baseDir = context.cacheDir;
    if (baseDir == null) {
        // 极端情况:某些设备无内部缓存,回退到 filesDir
        baseDir = context.filesDir;
    }
    File cacheDir = new File(baseDir, "okhttp_cache");
    if (!cacheDir.isDirectory()) {
        cacheDir.delete(); // 清除可能存在的文件
        if (!cacheDir.mkdirs()) {
            throw new RuntimeException("Failed to create cache directory: " + cacheDir.getAbsolutePath());
        }
    }
    long targetSize = calculateCacheSize(context); // 见下文
    return new Cache(cacheDir, targetSize);
}

private long calculateCacheSize(Context context) {
    long defaultSize = 20 * 1024 * 1024L; // 20MB
    // 动态计算:取可用空间的 1%,不超过 50MB
    File cacheDir = context.cacheDir;
    long usableSpace = cacheDir != null ? cacheDir.getUsableSpace() : 0;
    if (usableSpace > 0) {
        long dynamicSize = (long) (usableSpace * 0.01);
        return Math.min(dynamicSize, 50L * 1024 * 1024L); // 上限 50MB
    }
    return defaultSize;
}

3.4 验证缓存是否生效

初始化后可通过以下方式确认缓存工作正常:

  • 检查目录创建: cacheDir.exists() && cacheDir.isDirectory()
  • 打印缓存统计: cache.writeSuccessCount() 和 cache.writeAbortCount() 验证写入。
  • 查看日志: 启用 OkHttp 日志拦截器,观察 Cache-Control 和 Warning 头部。
  • 断言缓存非空: 首次请求后,cache.size() > 0。
// 监听缓存写入
client.dispatcher().executorService().execute(() -> {
    Log.d("OkHttpCache", "WriteSuccess=" + cache.writeSuccessCount()
                           + " Abort=" + cache.writeAbortCount()
                           + " Size=" + cache.size());
});

3.5 常见错误与踩坑

错误场景现象解决方案
目录创建失败cache() 抛出 IllegalArgumentException确保 mkidirs() 执行且返回 true;检查应用磁盘空间
缓存大小为 0 或负数OkHttp 内部忽略缓存(不报错)初始化时校验 size > 0,建议至少 1MB
多进程访问同一目录缓存混乱、冲突、文件损坏每个进程使用独立子目录,如 okhttp_cache_process_1
高并发写入导致 ANR主线程写缓存卡顿确保 OkHttpClient 不阻塞主线程,缓存 I/O 在 OkHttp 内置线程池执行
系统清理缓存缓存目录突然被删除每次使用前先检测目录有效性,如果不存在则重建

3.6 性能与稳定性优化建议

  • 隔离缓存目录: 不要与其他文件混放,单独 okhttp_cache 子目录。
  • 监控缓存状态: 通过 Cache.evictAll() 和 Cache.flush() 在适当的生命周期中清理。
  • 调试期开启日志: 使用 HttpLoggingInterceptor 记录缓存命中/未命中。
  • 避免频繁创建新 Client: OkHttpClient 应该是全局单例,Cache 对象复用。

3.7 Cache 初始化流程结构图

下图展示了从获取 Context 到构建可用 OkHttpClient 的完整流程:

OkHttp Cache 初始化流程 获取 Context 确定缓存目录 (cacheDir/okhttp) 计算缓存大小 (动态/静态) 创建目录 (mkdirs) new Cache(file, size) OkHttpClient.Builder().cache(cache) 权限检查 确保应用有写入权限 动态大小 建议 min(50MB, 1%可用空间)
核心要点: 缓存初始化看似简单,但目录权限、大小校准、多进程隔离是上线后最容易出问题的环节。把 createOkHttpCache() 封装成工具类,并在 Application.onCreate() 中延迟初始化,可以大幅提升稳定性。

3.8 本章小结