Okhttp3 缓存机制

先说业务使用上的结论

  1. Okhttp 只允许GET请求使用缓存
  2. okhttp 在使用缓存的时候,url 不能携带可变参数,否则无法命中缓存,如每次发起请求的时候,url内会包含一个时间戳,那么当前请求,无法命中缓存

OkHttp 缓存机制深度解析与 SOP 操作指南

本文基于 OkHttp 4.9.3 源码,深度解析其缓存设计逻辑,并提供实践指南。


一、缓存机制概述

1. 核心原则

  • 仅缓存 GET 请求:符合 HTTP 语义安全要求
  • 双重校验机制:存储 (Write) 与读取 (Read) 阶段双重过滤
  • 自动失效处理:对写操作(如 POST)自动清理相关缓存

2. 关键类说明

类名 作用
CacheInterceptor 拦截请求,协调缓存与网络决策
Cache 管理磁盘缓存存储
CacheStrategy 根据请求/响应头计算缓存策略

二、缓存工作流程图解

    A[发起请求] --> B[CacheInterceptor]
    B --> C{是否存在缓存?}
    C -- 有 --> D[检查缓存有效性]
    C -- 无 --> E[发起网络请求]
    D -- 有效 --> F[返回缓存]
    D -- 无效 --> E
    E --> G{是否为GET请求?}
    G -- 是 --> H[存储响应到缓存]
    G -- 否 --> I[跳过存储]

三、源码深度解析

  1. 缓存拦截入口:CacheInterceptor源码位置:okhttp3/internal/cache/CacheInterceptor.kt
override fun intercept(chain: Interceptor.Chain): Response {
    // 1. 尝试读取缓存
    val cacheCandidate = cache?.get(chain.request())

    // 2. 计算缓存策略
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()

    // 3. 决策分支
    when {
        strategy.networkRequest == null && strategy.cacheResponse == null -> {
            // 强制返回 504 无网络且无缓存
        }
        strategy.networkRequest == null -> {
            // 返回缓存(不发起网络请求)
        }
        else -> {
            // 发起网络请求并可能更新缓存
        }
    }
}
  1. 缓存存储逻辑 (Cache.kt)
// 路径:okhttp3/internal/cache/Cache.kt
internal fun put(response: Response): CacheRequest? {
    // 规则 1:仅处理 GET 请求
    if (response.request.method != "GET") return null
    
    // 规则 2:处理写操作缓存失效(POST/PUT/DELETE 等)
    if (HttpMethod.invalidatesCache(response.request.method)) {
        remove(response.request) // 删除关联缓存
        return null
    }

    // 规则 3:生成唯一缓存 Key
    val key = key(response.request.url) // 即 url.toString()
    
    // 磁盘写入操作(使用 DiskLruCache)
    val entry = Entry(response)
    return try {
        val editor = cache.edit(key) ?: return null
        entry.writeTo(editor)
        RealCacheRequest(editor)
    } catch (_: IOException) {
        abortQuietly(editor)
        null
    }
}
  1. 缓存策略决策 (CacheStrategy.kt)
// 路径:okhttp3/internal/cache/CacheStrategy.kt
internal class Factory(
    private val request: Request,
    private val cacheResponse: Response?
) {
    fun compute(): CacheStrategy {
        // 强制不缓存场景(如 no-store)
        if (request.cacheControl.noStore || cacheResponse?.cacheControl?.noStore == true) {
            return CacheStrategy(request, null)
        }

        // 缓存有效性验证
        if (!isCacheable(cacheResponse, request)) {
            return CacheStrategy(request, null)
        }

        // 计算缓存新鲜度
        val ageMillis = cacheAgeMillis()
        val freshMillis = cacheResponseMaxAgeMillis()
        val maxStaleMillis = if (request.cacheControl.maxStale != -1) {
            request.cacheControl.maxStale * 1000L
        } else Long.MAX_VALUE

        return when {
            ageMillis + minFreshMillis < freshMillis + maxStaleMillis -> 
                CacheStrategy(null, cacheResponse)
            else -> 
                CacheStrategy(request.newBuilder().build(), cacheResponse)
        }
    }
}

四、缓存配置 SOP

步骤 1:初始化缓存

// 创建 10MB 缓存实例
val cacheSize = 10 * 1024 * 1024L // 10MB
val cache = Cache(
    directory = File(context.cacheDir, "okhttp_cache"),
    maxSize = cacheSize
)

// 注入 OkHttpClient
val client = OkHttpClient.Builder()
    .cache(cache)
    .build()

步骤 2:服务端缓存控制

# 响应头示例(缓存 1 小时)
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
ETag: "xyz123"
Last-Modified: Wed, 21 Oct 2022 07:28:00 GMT

步骤 3:客户端强制刷新

val request = Request.Builder()
    .url(url)
    .header("Cache-Control", "no-cache") // 强制网络验证
    .build()

五、常见问题排查表

现象 可能原因 解决方案
缓存完全不生效 1. 未配置 Cache 实例
2. 服务端未返回有效的缓存头(如 Cache-Control
3. 缓存目录权限不足
1. 检查 OkHttpClient 是否注入 Cache 对象
2. 确保响应头包含 Cache-Control: max-age=xxx
3. 检查磁盘读写权限
部分请求无法缓存 1. 请求方法非 GET
2. URL 含随机参数(如 ?_t=timestamp
3. 响应状态码非法(如非200)
1. 仅对 GET 请求使用缓存
2. 自定义缓存 Key 过滤随机参数
3. 确保服务端返回 200/301 等可缓存状态码
磁盘缓存异常增长 1. 未清理过期缓存
2. 相同资源重复缓存(URL 参数不同)
3. 缓存最大尺寸设置过大
1. 调用 cache.evictAll() 清理
2. 统一 URL 参数规范
3. 调整 maxSize 为合理值(如10MB)
缓存数据过期但未更新 1. 缓存时间设置过长
2. 未使用 ETag/Last-Modified 验证机制
3. 客户端强制缓存过期数据
1. 缩短 max-age 时间
2. 添加条件请求头验证
3. 调用 request.cacheControl(CacheControl.FORCE_NETWORK)
POST请求后GET缓存未失效 1. 未触发 HttpMethod.invalidatesCache 机制
2. 服务端未更新资源版本
1. 确认 POST 等写操作后 OkHttp 自动调用 remove()
2. 服务端更新 ETagLast-Modified

六、源码设计精髓总结

双检锁机制
在 CacheInterceptor 和 CacheStrategy 中多次验证缓存有效性,确保逻辑严谨性。
磁盘缓存优化
采用 DiskLruCache 实现:
使用 LRU 淘汰算法
通过 journal 文件保证原子性
写入采用临时文件+原子提交
HTTP 协议完备性
完整支持:
条件请求 (If-Modified-Since/ETag)
Vary 头处理
分块传输编码缓存

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。