1. OkHttp 缓存整体架构与核心类
1.1 缓存定位与设计目标
OkHttp 的缓存系统内嵌于拦截器链中,位于网络请求与服务器之间。其核心目标是在满足 HTTP 缓存语义的前提下,通过本地磁盘复用响应数据,减少网络请求次数、降低延迟、节省带宽。
整个缓存体系由四个核心类协作驱动:
- Cache:负责响应数据的磁盘存储与读取管理。
- CacheStrategy:根据请求头与缓存响应头计算缓存是否可用、是否需要网络。
- Interceptor:
CacheInterceptor作为缓存决策的执行入口,串联Cache与CacheStrategy。 - Response 缓存头:服务端通过
Cache-Control、Expires、ETag、Last-Modified等头字段控制缓存行为。
1.2 核心类职责对比
| 核心类 | 职责 | 关键 API / 字段 |
|---|---|---|
Cache |
磁盘缓存的读写管理,基于 DiskLruCache 实现 |
Cache(File directory, long maxSize), get(Request), put(Response), remove(Request) |
CacheStrategy |
根据请求与缓存响应生成决策:使用缓存、网络请求或两者结合 | Factory.compute(CacheControl, CacheControl),输出 networkRequest 与 cacheResponse |
CacheInterceptor |
OkHttp 拦截器链中的缓存处理节点,执行策略并写入缓存 | intercept(Chain chain) 中调用 CacheStrategy 并维护 Response 封闭性 |
Response 缓存头 |
服务端通过 HTTP 头字段声明缓存策略 | Cache-Control: max-age, no-cache, no-store, must-revalidate 等 |
1.3 整体架构交互流程
用户发起请求后,OkHttp 将请求依次通过拦截器链。当请求到达 CacheInterceptor 时,缓存系统按以下顺序协作:
- 从 Cache 中读取候选响应:根据请求的 URL、请求头等条件,从磁盘缓存中查找匹配的
Response。 - 构建 CacheStrategy:将原始请求与缓存响应送入
CacheStrategy.Factory,计算当前缓存是否有效、是否需要携带条件请求头(如If-None-Match)重验证。 - 执行决策:根据
CacheStrategy输出的networkRequest和cacheResponse,决定直接返回缓存、发起网络请求或组合使用。 - 更新 Cache:若网络请求后得到新响应,
CacheInterceptor将其写入Cache;若服务器返回 304 Not Modified,则合并缓存响应头并返回。
1.4 代码配置与初始化
启用缓存的第一步是创建 Cache 对象并附加到 OkHttpClient.Builder:
File cacheDir = new File(context.getCacheDir(), "okhttp_cache");
long cacheSize = 10 * 1024 * 1024; // 10 MB
Cache cache = new Cache(cacheDir, cacheSize);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(new CacheInterceptor()) // 内部已内置,此处仅为示意
.build();
关键点:
Cache 构造函数第二个参数 maxSize 是软上限,OkHttp 会在后台线程中执行 LRU 清理,当磁盘缓存超出此大小时自动删除最旧条目。
1.5 调试缓存行为
通过添加日志拦截器或利用 Response.cacheResponse() 与 Response.networkResponse() 可以观察缓存命中状态:
Response response = client.newCall(request).execute();
if (response.cacheResponse() != null) {
System.out.println("缓存命中: " + response.cacheResponse().code());
} else if (response.networkResponse() != null) {
System.out.println("网络请求: " + response.networkResponse().code());
}
开启 OkHttp 日志框架(如 HttpLoggingInterceptor)可查看详细缓存头交换:
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.cache(cache)
.build();
1.6 常见错误
- 未设置缓存目录权限:
Cache初始化时若目录不可写,会抛出IOException,导致整个客户端构建失败。 - 缓存大小过小:
maxSize设置太小(如 <1MB)会导致频繁的 LRU 清理,实际缓存命中率极低。 - 混淆强制缓存与网络缓存:
Cache-Control: no-cache并非禁用缓存,而是要求每次使用前向服务器验证;no-store才是彻底禁止缓存。 - 忽略 CacheStrategy 的 null 检查:
CacheStrategy.networkRequest与CacheStrategy.cacheResponse可能为 null,自定义拦截器若未判空会触发空指针。
1.7 性能与稳定性优化
- 缓存目录选择:使用
context.getCacheDir()而非外部存储,避免权限问题与系统自动清理冲突。 - 合理设置 maxSize:根据应用资源类型计算,图片密集型应用建议 20~50 MB,API 数据型 5~10 MB。
- 使用 Control-Cache 头细粒度控制:服务端返回
max-age=3600, stale-while-revalidate=300可在缓存过期后仍提供旧数据,同时后台异步刷新。 - 离线场景降级:结合
CacheStrategy的onlyIfCached控制,在网络不可用时强制只返回缓存,避免请求挂起。
本章核心摘要:
Cache负责磁盘存储,CacheStrategy负责决策,CacheInterceptor执行决策并维护状态。- 响应缓存头是服务端策略的载体,
Cache-Control字段决定了缓存的存活时间与验证规则。 - 正确初始化 Cache 并利用日志分析缓存行为,是后续章节离线拦截实践的基础。