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的参数,可能会影响验签的逻辑。

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

推荐阅读更多精彩内容