缓存策略选型:缓存优先、网络优先、先网络后缓存、先缓存后网络

离线缓存的核心是选择合适的缓存策略,以平衡数据新鲜度与可用性。四种常用策略分别适用于不同业务场景:

策略 描述 适用场景 优点 缺点
缓存优先
Cache First
有缓存直接返回,不触发网络请求 新闻列表、配置信息等不频繁变化的数据 响应极快、零网络消耗 数据可能陈旧
网络优先
Network First
无条件发起网络请求,失败时回退到缓存 支付状态、实时评分等需要最新数据的场景 确保数据最新 离线时等待超时才会降级
先网络后缓存
Network → Cache
先请求网络,成功后更新缓存并返回;失败则返回缓存 用户首页 Feed、商品详情(允许短暂延迟) 平衡新老数据、降低网络失败影响 增加一次网络等待
先缓存后网络
Cache → Network
立即返回缓存,同时异步发起网络请求更新缓存(Stale-While-Revalidate) 列表页、搜索建议、可接受后台刷新的内容 首屏秒开、后台自动刷新 可能出现短暂的不一致
关键原理:四种策略本质上是 读取缓存 与 发起网络请求 两个操作的不同排列组合。在 OkHttp 拦截器中通过判断请求头中的 @CacheStrategy 注解或自定义 header 来路由到对应逻辑分支。

决策流程

下图展示了拦截器中根据策略标志进行分支的通用逻辑。每个策略对应一组缓存的读取/写入时机和优先级。

缓存策略选型决策流程 发起请求 策略类型 缓存优先 网络优先 先网络后缓存 先缓存后网络 读取缓存 → 返回 请求网络 → 成功返回 失败 → 读取缓存 请求网络 → 成功 → 更新缓存 失败 → 读取缓存 读取缓存 → 立即返回 异步网络 → 更新缓存 响应返回

拦截器实现核心代码

在自定义 Interceptor 中,通过读取请求头 X-Cache-Strategy 来分支。以下为关键逻辑骨架:

class CacheStrategyInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val strategy = request.header("X-Cache-Strategy") ?: "cache_first"
        
        return when (strategy) {
            "cache_first" -> cacheFirst(chain, request)
            "network_first" -> networkFirst(chain, request)
            "network_then_cache" -> networkThenCache(chain, request)
            "cache_then_network" -> cacheThenNetwork(chain, request)
            else -> cacheFirst(chain, request)
        }
    }
    
    private fun cacheFirst(chain: Interceptor.Chain, request: Request): Response {
        val cacheResponse = CacheManager.get(request.url.toString())
        if (cacheResponse != null) {
            return cacheResponse
        }
        return chain.proceed(request).also {
            CacheManager.put(request.url.toString(), it)
        }
    }
    
    private fun networkFirst(chain: Interceptor.Chain, request: Request): Response {
        try {
            val response = chain.proceed(request)
            CacheManager.put(request.url.toString(), response)
            return response
        } catch (e: IOException) {
            val cached = CacheManager.get(request.url.toString())
            if (cached != null) return cached
            throw e
        }
    }
    
    private fun networkThenCache(chain: Interceptor.Chain, request: Request): Response {
        try {
            val response = chain.proceed(request)
            CacheManager.put(request.url.toString(), response)
            return response
        } catch (e: IOException) {
            val cached = CacheManager.get(request.url.toString())
            if (cached != null) return cached
            throw e
        }
    }
    
    private fun cacheThenNetwork(chain: Interceptor.Chain, request: Request): Response {
        val cached = CacheManager.get(request.url.toString())
        if (cached != null) {
            // 异步更新缓存
            GlobalScope.launch(Dispatchers.IO) {
                try {
                    val response = chain.proceed(request)
                    CacheManager.put(request.url.toString(), response)
                } catch (_: IOException) {}
            }
            return cached
        }
        // 无缓存时直接走网络
        val response = chain.proceed(request)
        CacheManager.put(request.url.toString(), response)
        return response
    }
}
提示:实际项目推荐使用 Retrofit 的注解(如 @Headers("X-Cache-Strategy: cache_first"))来声明策略,或通过动态拦截器根据 URL 匹配规则自动选择策略。

调试方法

  • 在拦截器中添加日志,打印 请求URL、策略值 和 是否命中缓存。
  • 使用 OkHttp 的 addNetworkInterceptor 与 addInterceptor 分别观察网络层和缓存层的执行日志。
  • 测试离线场景:Android 模拟器中关闭网络,观察策略降级行为。

常见错误

⚠️ 错误1:缓存与网络数据混淆 — 未区分缓存键的唯一性(如未加上请求参数),导致不同请求读取到同一缓存。
⚠️ 错误2:先缓存后网络时未处理并发 — 异步网络回写缓存时未加锁,多次快速请求可能导致缓存覆盖或崩溃。
⚠️ 错误3:网络优先策略中无超时处理 — 离线时 chain.proceed() 会抛出异常但等待时间过长,建议设置 OkHttp 的 connectTimeout 和 readTimeout。

性能与稳定性优化

  1. 缓存大小与淘汰策略:使用 LruCache 或 DiskLruCache 限制内存/磁盘占用,避免 OOM。
  2. 网络请求取消:在 cache_then_network 策略中,当 Activity/Fragment 销毁时取消异步网络请求,防止更新已销毁页面的缓存。
  3. 缓存有效期:为不同策略设置不同的过期时间(例如缓存优先策略的 TTL 较短,网络优先的缓存 TTL 较长备用)。
  4. 避免死锁:确保缓存读取与网络请求不在同一线程阻塞,使用 Dispatchers.IO 或自定义线程池。
总结:四种策略各有适用场景,实际项目中可组合使用。例如首页使用 先缓存后网络 实现秒开,详情页使用 网络优先 保证最新,配置信息使用 缓存优先 提升性能。理解其原理后,可灵活定制约束条件(如弱网下自动切换为缓存优先)来增强应用的离线能力。