1. 缓存拦截器概述
1.1 OkHttp 缓存机制
OkHttp 内置了一套基于 HTTP 协议规范的缓存系统,其核心组件是 CacheInterceptor。该拦截器根据请求头(如 Cache-Control、If-Modified-Since、ETag)、响应头以及用户设置的缓存策略,决定是否复用本地缓存、发起网络请求或返回缓存响应。
缓存机制关键特性:
- 磁盘缓存:通过
Cache对象指定缓存目录与最大大小,OkHttp 会自动管理缓存文件的存储与淘汰(LRU 策略)。 - 策略控制:支持
CacheControl强制刷新、强制缓存、条件请求等。 - 透明集成:开发者只需在构造
OkHttpClient时设置Cache对象,CacheInterceptor 即自动加入拦截器链。
关键代码结构 —— 启用缓存仅需两行配置:
// 定义缓存目录和大小
File cacheDir = new File(context.getCacheDir(), "http_cache");
Cache cache = new Cache(cacheDir, 10 * 1024 * 1024); // 10MB
// 构建客户端时注入缓存
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.build();
上述代码执行后,OkHttp 内部自动将 CacheInterceptor 插入到拦截器链的正确位置。
1.2 拦截器链结构
OkHttp 使用责任链模式组织网络请求处理流程。每个拦截器在链中拥有固定职责,并按既定顺序依次执行。完整的默认拦截器链(按执行顺序)如下:
| 顺序 | 拦截器名称 | 职责 | 是否可移除 |
|---|---|---|---|
| 1 | 用户添加的应用拦截器(Application Interceptor) | 全局通用逻辑:日志、重定向、Header 注入等 | 否(用户通过 addInterceptor() 添加) |
| 2 | CacheInterceptor(缓存拦截器) | 读取磁盘缓存、判断缓存可用性、写缓存 | 否(启用 Cache 后自动加入) |
| 3 | ConnectInterceptor | 建立 TCP / TLS 连接 | 否 |
| 4 | BridgeInterceptor | 补全请求头(Cookie、Content-Length 等) | 否 |
| 5 | CallServerInterceptor | 执行真正的网络 I/O,发送请求并读取响应 | 否 |
| 6 | 用户添加的网络拦截器(Network Interceptor) | 观察原始网络请求与响应(位于连接之后) | 否(用户通过 addNetworkInterceptor() 添加) |
重要顺序特性:
- 应用拦截器在 最外层 调用
chain.proceed()一次;缓存拦截器处于链路中间。 - 当缓存命中且有效时,CacheInterceptor 直接返回缓存响应,不再调用后续拦截器(ConnectInterceptor 及之后不执行)。
- 网络拦截器在连接建立后、实际 I/O 之前执行,可观察或修改请求/响应。
1.3 缓存拦截器在链中的精确位置
缓存拦截器位于 应用拦截器之后、ConnectInterceptor 之前。这一位置设计保证了:
- 所有用户添加的全局拦截逻辑(如日志、 注入)在缓存判断之前完成,确保缓存键与请求头正确。
- 如果缓存可用,则直接返回缓存,跳过昂贵的连接建立和网络 I/O,提升性能并节省流量。
- 如果缓存不可用或过期,则继续向下传递请求,待网络响应返回后,CacheInterceptor 会负责将新响应写入缓存(在响应返回链向上传递时处理)。
✅ 最佳实践: 若需监听缓存命中情况,可在应用拦截器中检查响应头
X-Cache 或 Age 字段(由 CacheInterceptor 添加)。例如:
chain.proceed(request).newBuilder()
.header("X-Cache", response.cacheResponse() != null ? "HIT" : "MISS")
.build();
1.4 拦截器链流程示意图
下图展示了请求从应用拦截器到缓存的决策路径,以及缓存命中/未命中时的不同流向:
1.5 常见误区与避坑
❗️ 常见错误:
- 缓存拦截器被用户拦截器覆盖: 若通过
addInterceptor()添加自定义拦截器,并在其中再次调用proceed()且未遵循责任链模式(如重复创建新请求),可能导致缓存失效。 - 未区分应用拦截器与网络拦截器: 应用拦截器在缓存判断之前执行,网络拦截器在连接之后执行;若在应用拦截器中修改请求头影响缓存键,需谨慎。
- 缓存目录权限: 缓存文件夹如果无写入权限或空间不足,CacheInterceptor 会静默失败,可用
Cache.evictions()方法监控。
1.6 性能优化要点
- 合理设置缓存大小: 过小导致频繁淘汰(缓存命中率低),过大会占用大量磁盘空间,建议根据业务请求量估算,通常 10MB~50MB。
- 使用
CacheStrategy控制预缓存: 对频繁获取且变化较慢的资源,可强制设置max-age或stale-while-revalidate,减少网络请求。 - 调试缓存行为: 添加应用拦截器,打印
response.cacheResponse()和response.networkResponse()以确认缓存源。
1.7 调试指令
在开发阶段可通过 OkHttp 的日志拦截器(如 HttpLoggingInterceptor)观察缓存交互:
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.HEADERS);
OkHttpClient client = new OkHttpClient.Builder()
.cache(cache)
.addInterceptor(logging) // 应用拦截器,打印请求/响应头
.build();
响应头中若包含 Age: 123 且无 Cache-Control: no-cache,说明缓存命中。