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 的完整流程:
核心要点: 缓存初始化看似简单,但目录权限、大小校准、多进程隔离是上线后最容易出问题的环节。把
createOkHttpCache() 封装成工具类,并在 Application.onCreate() 中延迟初始化,可以大幅提升稳定性。