1、HTTP缓存基础与Retrofit拦截器概览
在深入 Retrofit 拦截器配置之前,必须先奠定 HTTP 缓存的理论基石。HTTP 缓存机制决定了客户端(App)能否复用先前请求的响应,从而避免重复发起网络请求。OkHttp 的拦截器体系正是操纵这些缓存行为的“插拔点”。
1.1 HTTP 缓存核心头
HTTP/1.1 定义了强制缓存和协商缓存两种策略。两者依赖的响应头不同,优先级也不同。
| 缓存类型 | 关键响应头 | 决策逻辑 |
|---|---|---|
| 强制缓存(本地判断) | Cache-Control: max-age=3600 |
计算当前时间与 Date 头差值,若 < max-age,直接使用本地缓存 |
| 强制缓存(过期) | Expires: Thu, 01 Dec 2025 16:00:00 GMT |
依赖客户端时钟精确性;若 max-age 同时存在,则 max-age 优先 |
| 协商缓存(去服务端验证) | ETag: "abc123" |
客户端发送 If-None-Match 头,服务端返回 304 Not Modified 或完整资源 |
| 协商缓存(时间验证) | Last-Modified: Wed, 15 Nov 2025 12:00:00 GMT |
客户端发送 If-Modified-Since,服务端对比时间决定 304 或 200 |
1.2 缓存策略分类与决策流程
OkHttp 内置的 CacheStrategy 类将上述头组合成三种最终决策:
- Network Only:仅从网络获取,不写缓存。适用实时数据。
- Cache Only:仅从本地缓存读取,不发起网络请求。适用离线模式。
- Cache + Network:优先读缓存,若过期则走协商缓存或强制请求。
关键门槛:只有理解了
CacheStrategy 的“请求—缓存—网络”三角关系,才能精确控制Interceptor 中的缓存替换逻辑。
1.3 Interceptor 在 OkHttp 中的地位
OkHttp 的拦截器分为两类,Application Interceptor(应用拦截器)与 Network Interceptor(网络拦截器)。缓存拦截器通常注册为应用拦截器,在请求发出前拦截并判断缓存策略;或在响应返回后更新缓存。
// 典型应用拦截器注册方式
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new CacheInterceptor()) // 自定义缓存拦截器
.cache(new Cache(context.getCacheDir(), 10 * 1024 * 1024)) // 10MB缓存目录
.build();
拦截器执行顺序:应用拦截器按照添加顺序依次执行,网络拦截器则按逆序执行。缓存拦截器建议放在应用层拦截器的最前端,以确保在写入缓存前有机会修改响应头。
1.4 课程最终交付物清单
通过本课程 30 章节的系统学习,您将获得以下可直接用于生产的交付物:
| 交付物 | 内容描述 | 适用场景 |
|---|---|---|
| CacheInterceptor | 可拔插的 OkHttp 应用层拦截器,支持 max-age、ETag 自定义解析 | 通用项目,需要精细控制缓存过期 |
| 网络策略注解 | @CacheStrategy 注解 + CachePolicy 枚举 |
多接口差异化缓存需求 |
| 日志/调试辅助模块 | 将缓存命中和失败 log 输出到控制台,带耗时和 header 详情 | 日常开发和线上问题排查 |
| 性能压测脚本 | 基于 Gradle Task 的并发缓存压力测试命令 | 上线前缓存稳定性验证 |
| 常见错误清单 | 包括 NO_CACHE 被忽略、Cache-Control 冲突等 10+ 超时案例 |
团队协作与代码 Review |
1.5 为什么从缓存拦截器开始
OkHttp 本身提供了 CacheInterceptor 实现,但其内置策略强制遵循 RFC 7234,无法满足以下常见需求:
- 离线优先:即使服务端返回
Cache-Control: no-store,仍希望本地缓存可用 - 自定义过期时间:根据业务代码动态设置
max-age,而非固定头 - 缓存预热:App 启动时从缓存中读取已知数据,避免白屏
- 流量节省:对图片等大资源强制使用协商缓存,降低带宽消耗
因此,掌握自定义缓存拦截器的原理和实现,是 Android 高级工程师流畅处理网络层的关键能力。
常见陷阱:不要混淆“内存缓存”与“磁盘缓存”。OkHttp 的
Cache 类默认使用磁盘持久化,而 CacheInterceptor 负责控制读写策略。如果同时使用内存缓存(如 LruCache),需要额外处理数据一致性,否则容易导致缓存过期数据不一致问题。