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 作为内存索引。其合作流程如下:
- 请求到达:OkHttp 首先检查内存索引(
requestCount和hitCount等计数器),判断该请求是否最近被缓存过。 - 内存命中:若内存中已有对应键的磁盘文件路径,则直接加载磁盘文件中的头部和响应体。
- 内存未命中:从磁盘中遍历所有条目,查找匹配的缓存键。若找到,则加载并将键-文件路径对写入内存索引。
- 缓存写入:当需要缓存新响应时,先写入磁盘文件,再更新内存索引。
- 淘汰策略:磁盘使用 LRU(最近最少使用)策略,通过
maxSize限制总字节数;内存索引也会同步清理已被淘汰的条目引用。
1.1.4 缓存体系工作流程图
下图展示了 OkHttp 缓存三层模型在请求-响应全生命周期中的协作关系。
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 以内)进行调整。