第2章 · Retrofit缓存拦截器架构:OkHttp Cache、Interceptor、NetworkInterceptor

2.1 架构原理

Retrofit 本身不直接处理缓存,而是委托给底层的 OkHttp 客户端。OkHttp 提供了一套三层拦截器体系,用于控制请求与响应的缓存行为:

  • Interceptor(应用拦截器):在请求发送前和响应返回后执行,位于整个请求链的最外层,适合注入通用缓存头或日志。
  • OkHttp Cache(缓存存储层):基于磁盘的缓存策略,根据 RFC 规范自动判断缓存有效性。
  • NetworkInterceptor(网络拦截器):在真正发起网络请求前和收到原始响应后执行,位于缓存层之后,可精确控制条件请求(ETag / Last-Modified)。
核心原则:Interceptor 对缓存的数据不可见,而 NetworkInterceptor 可以看到缓存未命中时的原始网络响应,因此更适用于缓存头校验逻辑。
Retrofit 缓存拦截器三层架构流程图 缓存拦截器三层架构流程 应用层 · Retrofit Interceptor · 应用拦截器链 缓存命中? OkHttp Cache 命中 NetworkInterceptor · 网络拦截器链 服务器 / 网络 缓存写入路径 说明: Interceptor 工作在缓存策略之前,适合通用头注入;NetworkInterceptor 工作在缓存策略之后,适合精细缓存校验。 缓存命中时直接返回 Cache 内容,不再触发 NetworkInterceptor 和网络请求。

2.2 核心组件职责对比

组件 执行位置 可见范围 典型用途
Interceptor 缓存策略之前 应用层请求/响应 注入通用 Cache-Control、日志记录、重试逻辑
OkHttp Cache 拦截器链中间 磁盘缓存存储与命中判断 根据 RFC 标准自动管理缓存有效期
NetworkInterceptor 缓存策略之后 原始网络请求/响应 处理 ETag、Last-Modified 条件请求

2.3 实现流程:构建带缓存的 Retrofit 实例

以下步骤展示了如何将三层拦截器与 Retrofit 整合:

  1. 创建 OkHttp Cache 实例,指定缓存目录与最大大小。
  2. 添加应用层 Interceptor 注入通用缓存头。
  3. 添加网络层 NetworkInterceptor 实现条件缓存校验。
  4. 构建 OkHttpClient 并传入 Retrofit.Builder。
// 步骤1:定义缓存目录与大小
File cacheDir = new File(context.getCacheDir(), "http_cache");
long cacheSize = 10 * 1024 * 1024; // 10 MB
Cache okHttpCache = new Cache(cacheDir, cacheSize);

// 步骤2:应用拦截器 — 注入通用缓存头
Interceptor appInterceptor = chain -> {
    Request original = chain.request();
    Request modified = original.newBuilder()
            .header("Cache-Control", "max-age=60")
            .build();
    return chain.proceed(modified);
};

// 步骤3:网络拦截器 — 条件缓存
Interceptor networkInterceptor = chain -> {
    Response originalResponse = chain.proceed(chain.request());
    return originalResponse.newBuilder()
            .header("Cache-Control", "public, max-age=300")
            .removeHeader("Pragma")
            .build();
};

// 步骤4:构建 OkHttpClient
OkHttpClient client = new OkHttpClient.Builder()
        .cache(okHttpCache)
        .addInterceptor(appInterceptor)
        .addNetworkInterceptor(networkInterceptor)
        .build();

// 步骤5:构建 Retrofit 实例
Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .client(client)
        .addConverterFactory(GsonConverterFactory.create())
        .build();

2.4 调试与验证

通过日志或抓包工具验证拦截器是否生效:

// 应用拦截器日志示例(每次请求均打印)
D/OkHttp: --> GET /users/list HTTP/1.1
D/OkHttp: --> Cache-Control: max-age=60
D/OkHttp: --> END GET

// 缓存命中时,NetworkInterceptor 不会触发
D/OkHttp: <-- 200 OK (cache)  // ← 无 networkInterceptor 日志

// 缓存未命中时,NetworkInterceptor 生效
D/OkHttp: --> GET /users/list HTTP/1.1
D/OkHttp: --> If-None-Match: "abc123"
D/OkHttp: <-- 304 Not Modified
D/OkHttp: --> Cache-Control: public, max-age=300
调试建议:使用 OkHttp 的日志拦截器(HttpLoggingInterceptor)分别添加到 Interceptor 和 NetworkInterceptor 位置,可以清晰看到两层拦截器的调用时机差异。

2.5 常见错误与避坑

错误场景 原因 解决方案
缓存始终不生效 服务器未返回 Cache-Control 头 在 NetworkInterceptor 中手动添加缓存头
Interceptor 中修改了缓存数据 误操作 Response body 导致缓存损坏 只修改 header,不触碰 body
NetworkInterceptor 不执行 缓存命中后直接返回,不触发网络层 确认缓存策略无强制缓存,或用 F12 清除缓存后重试
磁盘缓存写入失败 缓存目录不可写或权限不足 使用 Context.getCacheDir() 确保路径合法

2.6 性能与稳定性优化

  • 缓存大小控制:根据业务场景设置合理的 cacheSize(通常 5~50 MB),避免磁盘频繁 GC。
  • 拦截器顺序:通用头注入放在 Interceptor,条件校验放在 NetworkInterceptor,职责分离。
  • 避免在 Interceptor 中做耗时操作:拦截器运行在 OkHttp 的调度线程,长时间阻塞会耗尽连接池。
  • 配合 Stetho 或 Chucker:可视化查看缓存命中状态与响应头,加速调试。
  • 缓存目录隔离:不同业务模块使用独立的 Cache 实例,防止缓存污染。
⚠ 稳定性风险:不要在 Interceptor 或 NetworkInterceptor 中直接抛出未捕获的异常,会导致整个请求链路中断。建议使用 try-catch 包装后返回 fallback 响应或重试。

2.7 架构决策总结

选择 Interceptor 还是 NetworkInterceptor 取决于你的缓存策略目标:

  • 需要全局覆盖缓存头 → 用 addInterceptor() 注入通用 Cache-Control。
  • 需要精细条件缓存(ETag / Last-Modified) → 用 addNetworkInterceptor()。
  • 需要同时使用 → 按上述流程分层配置,互不冲突。