前言: 最近再学习http的缓存策略, 深入到android网络框架okhttp3中进行了解. 未进行学习的可先移步至okhttp缓存实现过程图
关键类是: CacheInterceptor
1. 这个类从哪儿来? 什么时候用到的?
这个类的使用是在Okhttp3的RealCall类中. 首先了解下RealCall类, 这个类是Call的子类. 一个Call就是一个待执行的request, Call接口中有个execute方法, 用来执行request请求.
下面是RealCall中的excute方法:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
//注意这里有个连接器链的方法
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
每次执行request请求的时候, 都会走getResponseWithInterceptorChain();方法, 我们去看看里面写了什么
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//这里添加了若干个拦截器进来,我们只对cacheIntercept进行了解
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
以上我们了解到在执行request请求的时候, okhttp3为我们添加了缓存拦截器, 我们来分析下拦截器中都做了些什么
2. CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
//这里获取到内部缓存候选对象, 没有为null, 有则取出response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//这一行很重要, 根据当前chain中的response和缓存中的response指定当前的缓存策略--request和response, request为null代表不使用网络, response代表没有本地缓存
// 会在后文中介绍缓存策略的具体内容
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//缓存的计数器+1
if (cache != null) {
cache.trackResponse(strategy);
}
//候选缓存存在, 且本地cache不存在, 关闭候选缓存的输入输出流
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
//如果禁用了网络且本地的cache也为空 请求失败, 返回504(only-if-cached代表不请求网络, 强制使用缓存)
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();
}
// 不使用网络 直接返回本地的cache
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
//如果走的这一步 , 说明需要请求网络, 接下来会进行网络reponse的处理
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());
}
}
//读完最后的缓存策略 可以知道会在请求头中根据本地缓存数据的header对request的请求header进行处理, 符合条件是会进行对比缓存
// 这里用来处理网络请求返回304状态, 304代表缓存命中
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果有本地缓存, 且网络请求命中缓存, 则将本地缓存和网络缓存的的头部进行合并后返回response
//重新设置response的请求响应时间等一系列参数
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()).
//缓存命中次数+1
cache.trackConditionalCacheHit();
//更新本地缓存信息
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//若没有命中缓存, 则使用网络请求返回的response做响应结果
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
//如果开启了缓存, 并且body不为空, 这里只有get请求会被缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response); 17744567781
return cacheWritingResponse(cacheRequest, response);
}
//缓存方法无效时 会把缓存移除
//无效方法有: return method.equals("POST")
// || method.equals("PATCH")
// || method.equals("PUT")
// || method.equals("DELETE")
//|| method.equals("MOVE");
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
总上可以看出来, CacheIntercepter根据CacheStrategy中的internetRequest和cacheResponse来对使用本地缓存或者网络数据来进行判断并返回合适的response, 同时更新本地的缓存, 接下来我们就需要知道缓存策略是如何生成的
3. CacheStrategy
Factory方法:
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
//这里的cacheResponse就是指本地的缓存
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);
}
}
}
}
在CacheIntercepter调用了一个获取缓存策略的方法get(), 看到get方法中有个getCandidate()方法
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// We're forbidden from using the network and the cache is insufficient.
return new CacheStrategy(null, null);
}
return candidate;
}
private CacheStrategy getCandidate() {
//在这里说明下, CacheStrategy(request, response)中的两个参数
//下面是源码中的注释:
/**The request to send on the network, or null if this call doesn't use the network. */
/** The cached response to return or validate; or null if this call doesn't use a cache. */
//request: 当request为null时则不使用网络,反之则使用
//response: 当response为null时不使用缓存,反之则使用
// 本地缓存为空, 直接采用网络请求, 使用网络数据
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//请求是https协议, 但是缓存未使用三次握手机制, 则使用网络
//注意: 这里就涉及到了https安全机制的一些问题, 在我的别的帖子中会有说明
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 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.
//根据response和request中的header缓存参数以及响应码判断是否可以使用缓存, 不能使用缓存则请求网络
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//获取请求中的缓存策略, 如果请求是noCache或者有使用对比缓存字段 If-None-Match或者If-Modified-Since, 则使用去请求网络
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//responseCaching的header中如果有immutable字段, 代表这个缓存永远不会过期, 则直接使用缓存数据
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
//以下是一系列从cacheResponse中获取出来的时效性的相关的一些参数
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());
}
//对于时效性进行判断, 有缓存但是缓存有过期时, header中添加警告信息后返回数据
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());
}
//如果本地缓存中的header头中有相关缓存对比的字段, 则取出来放置request的头中, 发起下次请求
//这里可以看出来 Etag的优先级要比Last-Modified高
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();
//返回经过处理的request和本地缓存
return new CacheStrategy(conditionalRequest, cacheResponse);
}
这里我们学习了okhttp3为我们实现的缓存策略, 究竟我们如何将这些缓存策略应用到项目中呢?
下篇简书中会对缓存的应用进行介绍, 敬请期待~
网友评论