OkHttp原理分析(四)

211 阅读5分钟

缓存使用

使用缓存拦截器之前,要在构造OkhttpClient时通过cache方法设置缓存,Cache构造函数接收两个参数,一个是缓存目录,一个是指定缓存大小。

OkHttpClient client = new OkHttpClient.Builder()
                        .cache(new Cache(mContext.getCacheDir(), 10240*1024))
                        .build();

HTTP缓存机制

1.强缓存

缓存规则信息包含在header中,而强缓存的规则通常由Expires和Cache-Control这两个字段标明

Expire

Expires表示到期时间,一般在Response的header中标识,当第二次请求时间超过此时间,则判定为缓存过期,需重新向服务器请求数据,否则直接返回缓存数据。这个字段存在的问题是这个时间是由服务器返回的时间,如果客户端和服务端时间存在误差,则会造成缓存机制的误差。

Expires: Thu, 13 Sep 2018 02:08:54 GMT

Cache-Control

Cache-Control标明缓存的持续时间,是一个相对值,比如max-age= 604800,表示缓存有效期可以持续604800秒即一周,在一周内再次请求这条数据,直接返回缓存数据。Cache-Control常用取值有private、public、no-cache、max-age,no-store

  • max-age:即上面提到的表明缓存的持续时间;
  • private:表示客户端可以缓存数据,默认为private;
  • public:表示所有内容都将被缓存,客户端和代理服务器都可缓存;
  • no-cache:字段表示客户端需验证服务器响应是否有更改(即下面说到的对比缓存);
  • no-store:不允许缓存
Cache-Control: private, max-age=0, no-cache

2.协商缓存

与强制缓存不同的是,协商缓存每次进行再请求时,需要先向服务器查询该缓存是否可用,如果缓存可用,则返回304状态码,通知客户端可以使用缓存,否则响应整片资源内容。协商缓存有这几个字段来标识缓存规则:Last-Modified  /  If-Modified-Since 、Etag  /  If-None-Match

Last-Modified  /  If-Modified-Since

服务器在响应客户端请求时,头部通过Last-Modified 告诉客户端资源的最后修改时间,客户端再次请求时,头部字段携带If-Modified-Since告诉服务器,上次返回的资源最后修改时间,让服务器进行对比,若当前资源最后修改时间大于If-Modified-Since,则说明资源被改动了,响应整片资源,返回200状态码,否则返回304状态码,通知客户端可以使用缓存。

Etag  /  If-None-Match

etag是服务器对资源的一种摘要,客户端请求时,返回响应中该字段告诉客户端缓存数据的标识。客户端再次请求通过If-None-Match与服务器匹配,匹配成功说明缓存可用,返回304,若服务端对数据发生更改,则匹配不成功,重新响应资源和缓存规则信息(优先级大于Last-Modified / If-Modified-Since)。

3.缓存机制总结

缓存机制总结

缓存拦截器

class CacheInterceptor(internal val cache: Cache?) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    // 从缓存中获取响应
    val cacheCandidate = cache?.get(chain.request())
    val now = System.currentTimeMillis()
    // 创建缓存策略
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
    // 记录请求次数,使用和不使用缓存次数
    cache?.trackResponse(strategy)
    if (cacheCandidate != null && cacheResponse == null) {
      // 缓存没有被应用,关闭它
      cacheCandidate.body?.closeQuietly()
    }
    // 如果禁止我们使用网络并且缓存不足,则失败。
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
    }
    // 如果我们不需要网络(缓存中有响应结果),我们就完成了。
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build()
    }
    var networkResponse: Response? = null
    try {
      networkResponse = chain.proceed(networkRequest)
    } finally {
      // 如果在I/O或其他方面崩溃,请不要泄漏缓存主体。
      if (networkResponse == null && cacheCandidate != null) {
        cacheCandidate.body?.closeQuietly()
      }
    }
    // 如果我们也有一个缓存响应,那么我们做一个条件get。
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build()
        networkResponse.body!!.close()
        // 记录缓存命中次数
        cache!!.trackConditionalCacheHit()
        // 更新缓存
        cache.update(cacheResponse, response)
        return response
      } else {
        cacheResponse.body?.closeQuietly()
      }
    }
    // 清空响应体
    val response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()
    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // 为请求提供缓存
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response)
      }
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          // 缓存移除请求
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // 缓存不能被写入
        }
      }
    }
    return response
  }
}
CacheStrategy.kt
// 工厂方法创建CacheStrategy对象
fun compute(): CacheStrategy {
  val candidate = computeCandidate()
  // 只使用缓存,cache-control请求头
  if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
    return CacheStrategy(null, null)
  }
  return candidate
}
private fun computeCandidate(): CacheStrategy {
  // 没有响应缓存
  if (cacheResponse == null) {
    return CacheStrategy(request, null)
  }
  // Https请求和响应没有握手
  if (request.isHttps && cacheResponse.handshake == null) {
    return CacheStrategy(request, null)
  }
  // 检测response的状态码,Expired时间,是否有no-cache标签
  if (!isCacheable(cacheResponse, request)) {
    return CacheStrategy(request, null)
  }
  //  判断请求头no-cache,If-Modified-Since和If-None-Match
  val requestCaching = request.cacheControl
  if (requestCaching.noCache || hasConditions(request)) {
    return CacheStrategy(request, null)
  }
  val responseCaching = cacheResponse.cacheControl
  // 返回响应的当前年龄,以毫秒为单位
  val ageMillis = cacheResponseAge()
  // 从服务日期开始,返回响应新鲜的毫秒数。
  var freshMillis = computeFreshnessLifetime()
  if (requestCaching.maxAgeSeconds != -1) {
    freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
  }
  var minFreshMillis: Long = 0
  if (requestCaching.minFreshSeconds != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
  }
  var maxStaleMillis: Long = 0
  if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
  }
  // 响应头cache-control,缓存有效
  if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
    val builder = cacheResponse.newBuilder()
    if (ageMillis + minFreshMillis >= freshMillis) {
      builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
    }
    val oneDayMillis = 24 * 60 * 60 * 1000L
    if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
      builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
    }
    return CacheStrategy(null, builder.build())
  }
  // 找到要添加到请求的条件。如果条件满足,则响应主体不会传输。
  val conditionName: String
  val conditionValue: String?
  when {
    etag != null -> {
      conditionName = "If-None-Match"
      conditionValue = etag
    }
    lastModified != null -> {
      conditionName = "If-Modified-Since"
      conditionValue = lastModifiedString
    }
    servedDate != null -> {
      conditionName = "If-Modified-Since"
      conditionValue = servedDateString
    }
    else -> return CacheStrategy(request, null) // 没条件!进行网络请求
  }
  val conditionalRequestHeaders = request.headers.newBuilder()
  conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
  val conditionalRequest = request.newBuilder()
    .headers(conditionalRequestHeaders.build())
    .build()
  // 带有条件的请求
  return CacheStrategy(conditionalRequest, cacheResponse)
}

缓存拦截器总结

strategy.networkRequeststrategy.cacheResponse结果
null null 配置了only-if-cached,强制使用缓存,但此时缓存不存在或过期,则返回504错误
not-nullnull 没有缓存命中,需要进行网络请求
null not-null 即强缓存,缓存存在且有效,直接返回缓存数据而不进行网络请求
not-null not-null 协商缓存,header含(ETag/Last-Modified)字段,需要向服务器确认缓存有效性,有效返回304状态码,使用缓存数据,否则返回200,响应整片资源