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