OkHttp3缓存控制

说明

需求是这的:软件启动时网络接口走本地缓存,下拉刷新时做真正的网络请求。

正常情况下用okhttp的缓存功能,但是比较坑的一点是:一旦命中缓存后NetworkInterceptor不再执行GitHub

    // If we don't need the network, we're done.
    // 有缓存的时候,直接将缓存返回
    if (networkRequest == null) {
      // ↓ 这里缓存命中,直接返回
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    //
    // .....
    // 

    var networkResponse: Response? = null
    try {
      // ↓网络拦截器调用位置
      networkResponse = chain.proceed(networkRequest)
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        cacheCandidate.body?.closeQuietly()
      }
    }

实现

追查cacheResponse的赋值情况,可找到缓存命中的规则:GitHub,其中比较重要的是key函数GitHub
再看一下缓存保存的时机:GitHub

    // 网络请求返回内容
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        // ↓ 放入缓存
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }
   }

其中重点看一下cache.put(response)这个方法:GitHub,这个方法比较长,这里截取一下片段:

  internal fun put(response: Response): CacheRequest? {
    val requestMethod = response.request.method
    // ...
    // 只缓存 GET 的请求方式
    if (requestMethod != "GET") {
      // Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
      // POST requests, but the complexity of doing so is high and the benefit is low.
      return null
    }
    // ...
    // 缓存写入逻辑
    val entry = Entry(response)
    var editor: DiskLruCache.Editor? = null
    try {
      //                   ↓ 这里是根据 response.request.url 生成key名称
      editor = cache.edit(key(response.request.url)) ?: return null
      entry.writeTo(editor)
      return RealCacheRequest(editor)
    } catch (_: IOException) {
      abortQuietly(editor)
      return null
    }
  }

知道了缓存的命中和保存步骤,那么实现就比较简单:

真正做网络请求的时候让缓存不命中,在网络回调来之后缓存命中更新缓存。
看懂了key的生成规则,那么就可以很容易的控制缓存是否命中,我这里使用的方法:

原始地址:https://www.baidu.com/s?wd=t
缓存地址:https://www.baidu.com/s?wd=t&__is_use_cache__=true   # 命中   - 走缓存
网络地址:https://www.baidu.com/s?wd=t&__is_use_cache__=false  # 不命中 - 走网络

代码实现:

/**
 * Okhttp拦截器:本地缓存拦截器
 *
 * @author 欢乐马_19e9
 * @date   2020-04-08
 */
class InterceptorOkHttpCacheOnline : Interceptor {
    companion object {
        const val CACHE_QUERY_NAME = "__is_use_cache__"
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        val oldRequest = chain.request()
        val newRequest = chain.request().newBuilder()

        // 如果当前不支持缓存,直接返回
        if (HttpMethod.invalidatesCache(oldRequest.method)) {
            return chain.proceed(oldRequest)
        }

        // 强制使用缓存
        // 当 CACHE_QUERY_NAME = false 的情况下不会命中缓存
        newRequest.cacheControl(CacheControl.FORCE_CACHE)

        // 获取返回状态信息
        val response: Response = chain.proceed(newRequest.build())

        // 如果返回的不成功,将不进行缓存
        if (response.code !in 200..399)
            return response

        // 修改url让缓存信息命中 CACHE_QUERY_NAME=true 的URL地址
        // 并将缓存内容自动保存
        val cacheUrl = oldRequest.url.newBuilder().removeAllQueryParameters(name = CACHE_QUERY_NAME).addQueryParameter(name = CACHE_QUERY_NAME, value = "true").build()
        newRequest.url(cacheUrl)

        // 强行增加缓存信息
        return response.newBuilder()
            .removeHeader(name = "Pragma")
            .removeHeader(name = "Cache-Control")
            .addHeader(name = "Cache-Control", value = "public, max-age=${CacheControl.FORCE_CACHE.maxStaleSeconds}")
            .request(newRequest.build()) // 修改原始请求地址,使其命中保存缓存
            .build()
    }
}

食用

    // 一定要为GET方式
    @GET(value = "/s")
    fun baiduQuery(
        @Query(value = "wd") word: String = "40",
        // 缓存变量,true表示走缓存
        @Query(value = InterceptorOkHttpCacheOnline.CACHE_QUERY_NAME) isUseCache: Boolean = false
    ): Single<String>

缺点

请求的时候会多携带一个CACHE_QUERY_NAME的参数,可能会影响验签的逻辑。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容