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 拦截器链流程示意图

下图展示了请求从应用拦截器到缓存的决策路径,以及缓存命中/未命中时的不同流向:

OkHttp 拦截器链与缓存拦截器位置 OkHttp 拦截器链与缓存拦截器位置 应用拦截器 缓存拦截器 返回缓存响应 连接拦截器 桥接拦截器 服务调用拦截器 网络拦截器 最终响应 📌 说明: 缓存拦截器(红色)位于应用拦截器之后、连接拦截器之前。缓存命中时直接返回(绿色分支), 未命中则继续向下直至网络 I/O,响应返回时由缓存拦截器写入磁盘。

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,说明缓存命中。