分析基于okhttp v3.3.1算法
Okhttp处理缓存的类主要是两个CacheIntercepter缓存拦截器,以及CacheStrategy缓存策略。 CacheIntercepter在Response intercept(Chain chain)方法中先获得chain中的request而后在Cache获取到Response,而后将Request和Respone交给建立CahceStrategy.Factory对象,在对象中获得CacheStrategy。代码看的更清晰:缓存
@Override public Response intercept(Chain chain) throws IOException {
//cache中取Response对象cacheCandidate
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//建立Cache.Strategy对象调用其get()方法获得对应的CacheStragy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
//取出strategy中的 Request和cacheRespone
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
...
复制代码
看下CacheStrategy.Factory使用原始的Request和在缓存中获得的Response对象CacheCandidate,怎样生成CacheStrategy的。 CacheStrategyFactory的生成bash
...
public Factory(long nowMillis, Request request, Response cacheResponse) {
//获取当前时间
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
//取出缓存响应当时服务器的时间
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
//取出过时时间
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
//取出最后一次更改时间
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
//取出etag
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
//
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
复制代码
缓存策略最终会产生三种策略中的一种:服务器
CacheStrategy中最后request为空表示可使用缓存,若是Response为空表示不能使用缓存 若是都为空 说明不能使用直接返回504app
具体判断ide
源码上加了注释ui
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
// 在缓存中没有获取到缓存
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// https不知足的条件下不使用缓存
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//Request和Resonse中不知足缓存的条件
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//在header中存在着If-None-Match或者If-Modified-Since的header能够做为效验,或者Cache-control的值为noCache表示客户端使用缓存资源的前提必需要通过服务器的效验。
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//缓存的响应式恒定不变的
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
//计算响应缓存的年龄
long ageMillis = cacheResponseAge();
//计算保鲜时间
long freshMillis = computeFreshnessLifetime();
//Request中保鲜年龄和CacheResponse中保鲜年龄取小
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
//取Request的minFresh(他的含义是当前的年龄加上这个日期是否还在保质期内)
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//当缓存已通过期且request表示能接受过时的响应,过时的时间的限定。
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//判断缓存可否被使用
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
//已通过期可是能使用
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
//缓存的年龄已经超过一天的时间
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// Find a condition to add to the request. If the condition is satisfied, the response body
// will not be transmitted.
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
复制代码
判断是否过时的依据:ageMillis + minFreshMillis < freshMillis + maxStaleMillis,(当前缓存的年龄加上指望有效时间)小于(保鲜期加上过时但仍然有效期限) this
//计算缓存从产生开始到如今的年龄。
long ageMillis = cacheResponseAge();
//计算服务器指定的保鲜值
long freshMillis = computeFreshnessLifetime();
//请求和响应的保鲜值取最小
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
//指望在指定时间内的响应仍然有效,这是request的指望
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//接受已通过期的响应时间,
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//规则,缓存的年龄加上指望指定的有效时间期限小于实际的保鲜值加上过时时间
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
//虽然缓存能使用可是已通过期了这时候要在header内加提醒
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
//缓存已经超过一天了虽然尚未过时加提醒
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
复制代码
肯定缓存的年龄的参数是一下四个。url
根据RFC 7234计算的算法以下。spa
private long cacheResponseAge() {
//接收到的时间减去资源在服务器端产生的时间获得apparentReceivedAge
long apparentReceivedAge = servedDate != null
? Math.max(0, receivedResponseMillis - servedDate.getTime())
: 0;
//age字段的时间和上一步计算的时间去大值
long receivedAge = ageSeconds != -1
? Math.max(apparentReceivedAge, SECONDS.toMillis(ageSeconds))
: apparentReceivedAge;
//接受时间减去发送时间
long responseDuration = receivedResponseMillis - sentRequestMillis;
//上一次响应时间到如今为止的差值
long residentDuration = nowMillis - receivedResponseMillis;
//三者相加获得cache的age
return receivedAge + responseDuration + residentDuration;
}
复制代码
private long computeFreshnessLifetime() {
//cacheRespone中的control
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.maxAgeSeconds() != -1) {
//若是cacheContro中存在maxAgeSecends直接使用
return SECONDS.toMillis(responseCaching.maxAgeSeconds());
} else if (expires != null) {
//若是没有max-age就使用过时时间减去服务器产生时间
long servedMillis = servedDate != null
? servedDate.getTime()
: receivedResponseMillis;
long delta = expires.getTime() - servedMillis;
return delta > 0 ? delta : 0;
} else if (lastModified != null
//若是上述条件都不知足则使用lastModified字段计算,计算规则就是服务器最后响应时间和资源最后更改时间的十分之一做为保质期
&& cacheResponse.request().url().query() == null) {
// As recommended by the HTTP RFC and implemented in Firefox, the
// max age of a document should be defaulted to 10% of the
// document's age at the time it was served. Default expiration // dates aren't used for URIs containing a query.
long servedMillis = servedDate != null
? servedDate.getTime()
: sentRequestMillis;
long delta = servedMillis - lastModified.getTime();
return delta > 0 ? (delta / 10) : 0;
}
//若是上述条件都不知足则直接返回0
return 0;
}
复制代码
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // 当networkRequest为空且cahceResponse为空的时候, //表示可使用缓存且如今的缓存不可用,返回504。 if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } //直接使用缓存,缓存是不可变的 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } //正常去作请求 Response networkResponse = 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) { closeQuietly(cacheCandidate.body()); } } // If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
//返回的结果responsecode 为304资源在服务器效验以后是没有改变的
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response 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();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
//更新缓存
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//若是不是304表示有变化
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//写入缓存
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//不能缓存移除
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
private static Response stripBody(Response response) {
return response != null && response.body() != null
? response.newBuilder().body(null).build()
: response;
}
复制代码
在CacheSategry里从respone中获取的,若是存在Etag或者Modify的字段就只用在ConditionalRequet设置对应的值作请求了,带有条件的请求,去服务器验证。
nocache的意思是不使用不可靠的缓存响应,必须通过服务器验证的才能使用
CacheStrategy#Factory#getCandidate()中
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
复制代码
在CacheStrategy中的判断逻辑是:当request中请求头中cache-control的值为no-cache的时候或者请求头中存在if-none-match或者if-modified-sine的时候直接去请求服务放弃缓存。
经过本篇咱们知道了http协议的缓存策略,已经在okhttp中是如何实践的。总的来讲会有这样几个步骤