OKhttp的缓存实现在CacheInterceptor中,okhttp进行请求时,会调用构建拦截器链,并执行每个拦截器的intercept方法。OKhttp的缓存策略就在CacheInterceptor中的intercept方法实现,下面我们来看一看CacheInterceptor中的intercept方法。
@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);
}
//如果当前缓存不符合要求,将其close
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 如果不能使用网络,同时又没有符合条件的缓存,直接抛504错误
// If we're forbidden from using the network and the cache is insufficient, fail.
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 we don't need the network, we're done.
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());
}
}
// 如果既有缓存,同时又发起了请求,说明此时是一个Conditional Get请求
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
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());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// 将网络响应写入cache中
// 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;
}
重要方法我已经注释,大家可以从代码中查看。
//首先尝试获取缓存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
这里的cache是传入的是final InternalCache cache;这个cache是一个接口,具体传入什么cache需要到cacheInterceptor的构造函数中去找。
interceptors.add(new CacheInterceptor(client.internalCache()));
OkHttpClient.internalCache()的实现
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
这里的cache就不是接口cache了,而是Cache类Cache类中的构造函数中创建了缓存DiskLruCache。而DiskLruCache内部使用的是LinkedHashMap
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);
int redundantOpCount;
接下来我们回到CacheInterceptor继续往下看,获取缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
几乎所有的动作都是以CacheStrategy缓存策略为依据做出的,那么接下来看下缓存策略是如何生成的,相关代码实现
private CacheStrategy getCandidate() {
// 没有缓存发起网络请求.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 如果是https,但没有TLS握手发起网络请求.
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//如果这条response没有被存储,那么它就无法作为response被使用,只要持久化良好并且规则不变,那么这条规则无用
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//如果当前的缓存策略是不缓存或者是conditional get,发起网络请求
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);
}
//ageMillis:缓存age
long ageMillis = cacheResponseAge();
//freshMillis:缓存保鲜时间
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
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());
}
//如果 age + min-fresh >= max-age && age + min-fresh < max-age + max-stale,则虽然缓存过期了,但是缓存继续可以使用,只是在头部添加 110 警告码
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.
// 发起conditional get请求
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);
}
这个cacheStrategy描述了缓存相关信息,具体操作详见上面代码注释。
总结:Okhttp的缓存在CacheInterceptor中实现,核心方法intercept,主要操作的cache是DiskLruCache,通过CacheStrategy来判断是从网络获取还是从缓存中获取,以及是否更改缓存内容。后面我将分析一下DiskLruCache。