现在的app没有几个是不联网的了,在流量费用很高、速度一般的今天给用户合理节省流量,以及提高响应速度就显得尤为重要了。所以一个优秀的app都会在发展到一定程度后就会开始引入缓存,什么是缓存呢?
百度百科:
缓存就是数据交换的缓冲区(称作Cache),当某一硬件要读取数据时,会首先从缓存中查找需要的数据,如果找到了则直接执行,找不到的话则从内存中找。由于缓存的运行速度比内存快得多,故缓存的作用就是帮助硬件更快地运行。
通俗一点就是:接杯水放在手边,渴了直接喝,没有去饮水机取。
原理
Okhttp3的网络缓存是基于http协议,如果不清楚,请自行搜索。
对于缓存,可阅读,缓存简介。
使用DiskLruCache缓存策略
注意点
- 目前只支持GET方式,其他请求方式需要自己实现
- 需要服务器配合,通过header相关的头来控制缓存
- 创建okhttpclient时候需要配置Cache
流程
1、如果配置缓存,则从缓存中取一次,不保证存在
2、缓存策略
3、缓存监测
4、禁止使用网络(根据缓存策略),缓存又无效,直接返回
5、缓存有效,不使用网络
6、缓存无效,执行下一个拦截器
7、本地有缓存,根具条件选择使用哪个响应
8、使用网络响应
9、 缓存到本地
源码
@Override
public Response intercept(Chain chain) throws IOException {
// 1、如果配置缓存,则从缓存中取一次,不保证存在
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 2、缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
// 3、缓存监测
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 4、禁止使用网络(根据缓存策略),缓存又无效,直接返回
// 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();
}
// 5、缓存有效,不使用网络
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
// 6、缓存无效,执行下一个拦截器
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());
}
}
// 7、本地有缓存,根具条件选择使用哪个响应
// 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());
}
}
// 8、使用网络响应
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
// 9、 缓存到本地
if (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
步骤分析
读取缓存
// 入口
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
// 主要是Cache类
- 通过url生成key(MD5、HEX)
- 通过key从内存中读取包装实体类Entry,内存中使用LinkedHashMap<String, Entry>
- 通过实体得到一个Snapshot,关联起文件系统中的缓存文件(缓存文件有多个,请求头文件、响应提文件),然后生成流(Source,Okio中的类,时间上就是inputStream)
- 通过快照得到一个Response实例
- 匹配是否是符合要求的,是返回响应,否关闭
// 位置 okhttp3/Cache
Response get(Request request) {
// 1、
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
// 2、
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
try {
// 3、
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
// 4、
Response response = entry.response(snapshot);
// 5、
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}
缓存策略的配置
如果上一步能够得到缓存响应,则配置策略,主要是解析缓存中与响应有关的头(Date\Expires\Last-Modified\ETag\Age)
// 2、缓存策略
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
-
解析缓存中与缓存有关的头
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 = value; } else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HttpHeaders.parseSeconds(value, -1); } } } }
-
根据一些条件实例一个CacheStrategy(get())
private CacheStrategy getCandidate() { // No cached response. // 1、没有缓存响应,返回一个没有响应的策略 if (cacheResponse == null) { return new CacheStrategy(request, null); } // 2、如果是https,丢失了握手缓存则,返回一个没有响应的策略 // Drop the cached response if it's missing a required handshake. if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } // 3、不能被缓存 // If this response shouldn't have been stored, it should never be used // as a response source. This check should be redundant as long as the // persistence store is well-behaved and the rules are constant. if (!isCacheable(cacheResponse, request)) { return new CacheStrategy(request, null); } // 4、缓存控制 CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } // 5、根据响应头 long ageMillis = cacheResponseAge(); 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; CacheControl responseCaching = cacheResponse.cacheControl(); 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); }
只有一种情况是会有正常的缓存被使用:所有的缓存头符合要求,即第5条。
缓存监测
// 3、缓存监测
if (cache != null) {
cache.trackResponse(strategy);
}
此处记录缓存使用情况
synchronized void trackResponse(CacheStrategy cacheStrategy) {
requestCount++;
if (cacheStrategy.networkRequest != null) {
// If this is a conditional request, we'll increment hitCount if/when it hits.
networkCount++;
} else if (cacheStrategy.cacheResponse != null) {
// This response uses the cache and not the network. That's a cache hit.
hitCount++;
}
}
禁止使用网络(根据缓存策略),缓存又无效,直接返回
根据上面缓存策略的配置,这种情况不会发生,不清楚为什么有这个逻辑
缓存有效,不使用网络
通过缓存策略,如果符合要求将会把Request置空,Response不为空,所以直接使用缓存
// 5、缓存有效,不使用网络
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
缓存无效,执行下一个拦截器
如果缓存无效,将会执行下一个拦截器,等待响应结果
本地有缓存,根具条件选择使用哪个响应
// 本地有缓存,响应结果没有修改,合并两个响应
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();
缓存到本地
// 9、 缓存到本地
// 1.
if (HttpHeaders.hasBody(response)) {
// 2.
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
-
根据头判断是否支持缓存
- 必须有响应体
- 内容有变化
-
是否符合缓存要求,根据策略
private CacheRequest maybeCache(Response userResponse, Request networkRequest, InternalCache responseCache) throws IOException { // 1、没有响应体 不缓存 if (responseCache == null) return null; // 2、是否支持 // Should we cache this response for this request? // 2.1、根据头 if (!CacheStrategy.isCacheable(userResponse, networkRequest)) { // 2.2、根据请求方式,有请求体的方式都不支持 if (HttpMethod.invalidatesCache(networkRequest.method())) { try { responseCache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } return null; } // 写入缓存 // Offer this request to the cache. return responseCache.put(userResponse); }
根据请求方式,有请求体的方式都不支持缓存
-
通过配置好的cache写入缓存
写入缓存和读取缓存使用的方式类似,都是通过Cache,DiskLruCache
CacheRequest put(Response response) { String requestMethod = response.request().method(); if (HttpMethod.invalidatesCache(response.request().method())) { try { remove(response.request()); } catch (IOException ignored) { // The cache cannot be written. } return null; } if (!requestMethod.equals("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; } if (HttpHeaders.hasVaryAll(response)) { return null; } Entry entry = new Entry(response); DiskLruCache.Editor editor = null; try { editor = cache.edit(key(response.request().url())); if (editor == null) { return null; } // 提交缓存 entry.writeTo(editor); return new CacheRequestImpl(editor); } catch (IOException e) { abortQuietly(editor); return null; } }
总结
缓存实际上是一个比较复杂的逻辑,单独的功能块,实际上不属于okhttp上的功能,只是通过http协议和DiskLruCache做了处理而已。