1.1 OkHttp 缓存体系概览

OkHttp 的缓存体系是构建高效、低延迟网络层的关键基础设施。它通过在客户端本地存储 HTTP 响应,减少不必要的网络请求,从而降低带宽消耗并提升应用响应速度。理解其缓存作用域、键生成规则以及磁盘与内存的合作模式,是掌握缓存实战的基础。

1.1.1 缓存作用域与三区模型

OkHttp 的缓存并非单一存储区,而是由 网络层、磁盘缓存 和 内存缓存 三层协作构成。每一层都有明确的职责与生命周期。

缓存层 作用域 存储内容 生命周期
网络层 请求/响应交互 HTTP 协议协商、条件请求(If-None-Match / If-Modified-Since) 单次网络往返
磁盘缓存 应用长期存储 序列化后的完整响应体及头部(受 Cache-Control 策略控制) 应用进程重启后依然存活
内存缓存 当前进程运行时 最近访问的响应索引、请求-响应映射(仅元数据) 进程退出后释放
实战关键点: 网络层缓存不直接存储响应体,而是通过条件请求(例如 304 Not Modified)避免重复传输未变更的响应体。磁盘缓存是持久化主力,内存缓存仅用于高速索引和轻量级命中判断。

1.1.2 缓存键生成规则

每个 HTTP 请求在进入缓存系统时,会被转换成一个唯一的缓存键。OkHttp 的缓存键由以下要素构成:

  • URL(scheme + host + port + path + query):核心标识,区分不同资源。
  • HTTPS 特殊处理:对于 HTTPS 请求,OkHttp 额外记录 TLS 版本和密码套件,避免缓存被跨连接复用。
  • 请求方法:GET 和 HEAD 请求被允许缓存;POST、PUT、DELETE 等不可缓存。
  • 响应头 Cache-Control:参与键生成的属性(如 Vary 字段),用于区分不同客户端协商结果。
// 缓存键生成核心逻辑摘要(OkHttp 源码 CacheStrategy.kt 中体现)
fun cacheKey(request: Request): String {
    val url = request.url
    val scheme = url.scheme()     // "http" 或 "https"
    val host = url.host()
    val port = url.port()
    val path = url.encodedPath()
    val query = url.encodedQuery() ?: ""
    return "$scheme://$host:$port$path?$query"
}
注意: Vary 头部会使得缓存键变得复杂。例如,服务器返回 Vary: Accept-Encoding,则同一 URL 但不同 Accept-Encoding 的请求会被视为不同缓存项。这会导致缓存命中率下降,生产环境中需谨慎设计。

1.1.3 磁盘与内存缓存合作模式

OkHttp 不直接使用 Android LruCache 或 DiskLruCache,而是通过 Cache 类(内部使用 DiskLruCache 库)管理磁盘持久化,同时在运行时维护一个轻量级 HashMap 作为内存索引。其合作流程如下:

  1. 请求到达:OkHttp 首先检查内存索引(requestCount 和 hitCount 等计数器),判断该请求是否最近被缓存过。
  2. 内存命中:若内存中已有对应键的磁盘文件路径,则直接加载磁盘文件中的头部和响应体。
  3. 内存未命中:从磁盘中遍历所有条目,查找匹配的缓存键。若找到,则加载并将键-文件路径对写入内存索引。
  4. 缓存写入:当需要缓存新响应时,先写入磁盘文件,再更新内存索引。
  5. 淘汰策略:磁盘使用 LRU(最近最少使用)策略,通过 maxSize 限制总字节数;内存索引也会同步清理已被淘汰的条目引用。

1.1.4 缓存体系工作流程图

下图展示了 OkHttp 缓存三层模型在请求-响应全生命周期中的协作关系。

OkHttp 缓存三层协作流程 发起 HTTP 请求 检查内存缓存索引 内存命中:直接加载 内存未命中:查磁盘 内存命中 → 返回响应 磁盘查找结果 磁盘命中 → 加载+更新内存 磁盘未命中 → 发起网络请求 写入磁盘 + 更新内存索引 内存缓存仅存索引,磁盘缓存持久化完整响应;LRU 淘汰以磁盘 size 为边界

1.1.5 缓存拦截器与配置段

在 OkHttp 中,缓存功能通过拦截器链实现。核心配置段位于 OkHttpClient.Builder 中:

// 标准缓存配置示例
val cacheDir = File(context.cacheDir, "http_cache")
val cache = Cache(cacheDir, maxSize = 10L * 1024 * 1024) // 10MB 磁盘缓存

val client = OkHttpClient.Builder()
    .cache(cache)                                    // 挂载缓存管理器
    .addInterceptor(LoggingInterceptor())            // 自定义拦截器(可选)
    .addNetworkInterceptor(CacheInterceptor())       // 网络层缓存拦截器
    .build()
性能优化提示: 磁盘缓存大小的选择需要权衡:过大会占用用户存储空间,甚至因 IO 压力导致性能下降;过小则命中率低。建议根据应用的网络流量特征(如图片类 App 可将缓存设为 50~100MB,纯 API 请求 10MB 以内)进行调整。