OkHttp中提供了网络请求的缓存机制,当咱们在上篇中追溯请求的流程时,知道每一个Request都须要进过CacheInterceptor.process()的处理,可是整个缓存处理确定是不止缓存拦截器的这一个方法的逻辑,它还涉及到:java
让咱们设想一个场景:一个用户一天内都打开屡次某个页面,而这个页面的内容相对固定,并非每次都更改。那么咱们有必要每次都从服务器中下载资源吗?答案是不用的。此时缓存就排上用场了。上面场景只是缓存的其中的一个好处,合理的使用缓存还能有以下好处:算法
【2.2.1】简介: 通常地,当客户端向服务端请求时,按照是否从新向服务器发起请求来划分,那么有强制缓存和协商缓存两种缓存类型。他们的优劣势各不相同。而强制缓存:当缓存处在且未失效的条件下,直接使用缓存做为返回并且http返回的状态码为200,不然请求服务器。简要的请求流程以下:数据库
【2.2.2】优缺点:缓存
【2.2.3】相关请求头bash
【2.3.1】简介: 协商缓存:当缓存存在时,带上用缓存标识先向服务器请求,服务器对比资源标识,若是不须要下发新资源,那么会直接返回304状态码,告诉客户端可用缓存;不然将新的资源和新的资源标识一块儿返回,此时的状态码为200。简要的请求流程以下: 服务器
【2.3.2】优缺点:网络
有了上面的一些Http缓存基本知识,接下来就能够跟随Okhttp的代码,来看看它是怎么处理缓存的了。ide
可选字段 | 意义 |
---|---|
no-cache | 不使用缓存,直接向服务器发起请求。 |
no-store | 不储存缓存 |
max-age = xxx | 告诉服务器,请求一个存在时间不超过xxx秒的资源 |
max-stale = xxx | 告诉服务器,可接受一个超过缓存时间为xxx秒的资源,若是xxx秒没有定义,则时间为任意时间 |
min-fresh = xxx | 告诉服务器,但愿接收一个在小于xxx秒内被更新过的资源 |
header 1 | header 2 |
---|---|
可选字段 | 意义 |
no-cache | 不直接使用缓存,须要向服务器发起请求校验缓存。 |
no-store | 服务器告诉客户端不缓存响应 |
no-transform | 告知客户端在缓存响应时,不得对数据作改变 |
only-if-cached | 告知客户端不进行网络请求,只使用缓存,若是缓存不命中,那么返回503状态码 |
Max-age=xxx | 告知客户端,该响应在xxx秒内是合法的,不须要向服务器发起请求。 |
public | 表示任何状况下都缓存该响应 |
private=“xxx” | 表示xxx或者不指明是为所有,值对部分用户作缓存。 |
CacheInterceptor.java
@Override public Response intercept(Chain chain) throws IOException {
//1.从LruCache中,根据Request,取出缓存的Response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//2.缓存选择策略类,根据request和response来决定需不须要使用缓存。
//new CacheStrategy.Factory() 详见:【3.2】
//CacheStrategy.Factory.get() 详见:【3.3】
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);
}
//4.缓存数据库里的响应缓存不为空,可是结果缓存策略选择后的结果为空
//证实这个响应缓存已通过时不适用了,将起关闭,防止内存泄露。后续的操做中也会将不用的Response进行关闭,就不一一赘述。
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body());
}
// 5.若是禁用了网络,此时request为空,而缓存的响应也为空,直接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();
}
// 6.若是不须要网络,且缓存的响应有效,返回这个缓存的响应。
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//7.到这一步,说明须要执行真正的网络请求,获得网络的响应了,因此执行下一个拦截器逻辑。
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
...
}
// 8.若是缓存的Response不为空,此时要综合网络返回回来的Respnse进行选择。
if (cacheResponse != null) {
//当服务器告诉客户端数据没有改变时,客户端直接使用缓存的Response。
//可是会更新最新的一些请求头等数据到缓存的Response。
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();
...
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//9.到这一步,肯定使用网络的Response。
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//10.缓存新的Response
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.
}
}
}
//返回最新的Respnse。
return response;
}
复制代码
总结:总的来讲,CacheIntercepter根据缓存策略选择出来的Request和Response来决定是否用缓存,和缓存的更新。详细的,它作了以下事情:性能
CacheStrategy.Factory.java
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);
}
}
}
复制代码
总结: 记录起request和缓存的Resonse,而后解析出cacheReonse响应头里面有关缓存的键值对并保存起来。以前在一中讲到的如ETag、Last-Modified等在这里就出现了。优化
CacheStrategy.Factory.java
public CacheStrategy get() {
【详见3.4】获取候选的请求和缓存响应。
CacheStrategy candidate = getCandidate();
/**这里若是networkRequest != null 表明缓存不可用,须要进行网络请求。
可是须要检查cacheControl是否指明了只是用缓存,不用网络。若是是的话,此时综合2个判断,能够得出请求失败。
而netWorkRequest = null && 擦车 Response = null 的处理结果咱们能够在【3.1】的5中能够看到,返回的是504.
*/
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
return new CacheStrategy(null, null);
}
return candidate;
}
复制代码
总结: 这里作获取候选的请求和cacheResponse。而且判断若是须要进行网络请求,而且在只用缓存的状况下,缓存不可用,那么直接返回2个空的候选结果。这里的CacheControl是对于Http里cacheControl请求头字段的描述。
CacheStrategy.Factory.java
private CacheStrategy getCandidate() {
//1. 该request没有缓存的响应,那么返回没有缓存的策略
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//2. 若是请求是Https,而且缓存的握手已经丢失,那么也返回一个没有缓存的策略。
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//3. 这里进行响应是否可缓存的判断。
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//4.若是request要求不用缓存;
//或者请求里面带有上次请求时服务器返回的"If-Modified-Since" || "If-None-Match"
//那么他们须要返回一个没有缓存的策略。
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
//得到响应的年龄
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;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//5.在缓存响应可被缓存的条件下
//若是知足(cacheResponse的年龄+最小刷新时间)<(最近刷新时间+最大验证秒数)那么能够不用进行网络请求而直接用cacheResonse。
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\"");
}
//返回一个不用网络请求,直接用cacheResponse的策略,这时进行强制缓存。
return new CacheStrategy(null, builder.build());
}
/**
* 6.到这里,进行协商缓存的判断。能够看到它们的优先级是:etag>lastModified>serverDate。
*/
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);
}
//若是存在上述说的请求头,那么将他加进入请求里面。
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
// 7.返回一个须要进行网络请求而且存在缓存响应的策略,此时他们将进行协商缓存
return new CacheStrategy(conditionalRequest, cacheResponse);
}
复制代码
总结: 缓存策略中,针对请求头和缓存响应头的一些值,进行缓存策略的选择,并返回。总的来讲他们作了以下判断:
netWorkRequest和cacheResponse的不一样组合状况得出的结果以下表:
netWorkRequest | cacheResponse | 表现结果 |
---|---|---|
空 | 空 | 不用网络且缓存不可用,直接返回503 |
空 | 不为空 | 不走网络,直接返回缓存 |
不为空 | 空 | 缓存不可用,常规请求。 |
不为空 | 不为空 | 须要协商缓存,进行网络访问,进一步确认。 |
本篇小节: 在本篇中,咱们按照是否须要向服务器请求,介绍了Http缓存的2种缓存方式:强制缓存和协商缓存。而后从CacheInterceptor.java入手,输入理解了Okhttp在缓存逻辑中作的一些事情:CacheStrategy获取缓存,CacheInterceptor根据缓存策略得到的候选Request和Response做出响应的逻辑处理,或是返回缓存响应,或是返回错误,或是进行协商缓存等