研究完自定义View,我们接着看okhttp的源码,之前已经讨论了okhttp的使用流程,和网络数据的读取,这一次来看一看okhttp的缓存机制。
首先我们回到之前已经阅读过的InterceptorChain,RealCall.java中的getResponseWithInterceptorChain()方法:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
// 重试的Interceptor,在构造方法中创建
interceptors.add(retryAndFollowUpInterceptor);
// 桥接Interceptor,主要对request的header进行操作,添加一些header信息
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 缓存Interceptor,判断是否要使用缓存
// 如果使用缓存,直接返回缓存的response,后面的Interceptor就不会得到执行
interceptors.add(new CacheInterceptor(client.internalCache()));
// 连接Interceptor
interceptors.add(new ConnectInterceptor(client));
// 我们在okhttp自定义的interceptor,不设置的话,可以忽略
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
// 呼叫服务端的Interceptor,主要是读取从服务端返回的数据
interceptors.add(new CallServerInterceptor(forWebSocket));
// Interceptor递归的开始
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
通过注释我我们顺便复习一下责任链的流程:
自定义Interceptor(可选) -> 重试Interceptor -> 桥接(准备)Intercepto -> 缓存Interceptor -> 链接Interceptor -> 自定义网络任务Interceptor(可选) -> 读取Interceptor
今天我们分析的就是缓存的CacheInterceptor,首先我们看到了他的构造方法里传入OkHttpClient.internalCache(),这个Cache需要在构建OkHttpClient的传入,默认是空的,我们可以自己实现InternalCache接口,自定义缓存的机制,也可以使用okhttp自带的Cache类进行缓存:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.cache(new Cache(File, maxSize)).build();
Cache的构造方法有两个:
public Cache(File directory, long maxSize) {
this(directory, maxSize, FileSystem.SYSTEM);
}
Cache(File directory, long maxSize, FileSystem fileSystem) {
this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
}
我们看到最终调用的还是创建DiskLruCache,这个类大家一定都不陌生,这是开源的缓存文件的类,他内部有完善的算法,帮助我们删除最不需要的缓存的对象,保持固定的缓存大小,节省用户的硬存空间。
其中FileSystem这个参数用的非常少,他主要负责从DiskLruCache读写缓存数据,你可以自定义实现FileSystem接口,或者直接使用okhttp内部实现的FileSystem.SYSTEM。
一般我们只是用第一个构造方法,既然了解了他的缓存使用的是DiskLruCache,我们就没有看下去的必要了。所以直接回到CacheInterceptor:
// 是否有缓存,request是key
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
// 记录当前时间
long now = System.currentTimeMillis();
// 把request和repsonse封装到起来,判断是否符合要求
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
// 增加计数器
cache.trackResponse(strategy);
}
// 如果从cache中得到了缓存,但是缓存策略中却没通过,说明这个缓存无效
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
// 如果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();
}
// 如果仅仅需要缓存,这里就直接返回缓存的response就可以了
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
以上是执行chain.proceed(networkRequest)之前的代码,看看他都做了哪些工作。
首先从缓存中取出之前的缓存,然后把request和取出的response封装到CacheStrategy.Factory中处理,判断我们的request和response是否符合要求,贴出判断的代码:
private CacheStrategy getCandidate() {
// No cached response.
// 没有缓存Response
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
// 如果是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
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
// 判断是否要使用缓存,不使用,response等于null
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
// 是否请求的response是不可改变的,这个值默认都是false
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
// 计算缓存的存活时间
long ageMillis = cacheResponseAge();
// 计算缓存存活的时间
long freshMillis = computeFreshnessLifetime();
// 如果request缓存有设置存活时间,取最小值
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());
}
// 如果使用缓存,且存活时间+最小存活时间 < 存活时间 + 最大腐败时间,使用缓存
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
// 存活时间+最小刷新时间 < 可以刷新的时间
if (ageMillis + minFreshMillis >= freshMillis) {
// 添加header信息,缓存已经开始腐败了
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
// 如果存活时间大于一天 且 不需要过期
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
// 添加header信息,希望能过期
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.
}
// 找到对应的缓存情况,把之前的header添加到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);
}
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
// 这里判断网络请求,是否只能使用缓存,这里是不支持的,所以之后会抛出504异常
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;
}
代码中判断各种情况,信息都是从request的header中和response的header中得到的信息,他们被封装到CacheControl类中。
如果我们的request请求是合法的,之后就会通过责任链得到最终的结果response:
Response networkResponse = null;
try {
// 得到最终的response结果
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
// 判断如果得到的response是空的,但是缓存却不为空,扔掉缓存
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// 如果之前有缓存的response
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
// http code 304 是请求网页的返回值,表示从上次请求后,网页的内容没有发生任何变化
// 客户端可以忽略此处判断
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
// 混合了request和response的header信息
.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 response = networkResponse.newBuilder()
// 清空之前的缓存
.cacheResponse(stripBody(cacheResponse))
// 清空请求到的内容
.networkResponse(stripBody(networkResponse))
.build();
// 如果缓存池不等于null
if (cache != null) {
// 如果这个response是可以被缓存的
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
// 更新缓存信息
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
// 如果请求的方法不需要缓存,移除缓存,例如post,put
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
之后的操作比较简单,我们把得到的结果response复制一份,关闭连接的response,然后根据情况更新或清除缓存。
okhttp的缓存机制意料之外的简单,他只有硬存缓存,并没有内存缓存,这是他缓存机制的一大缺陷,当然我们可以通过自定义缓存机制来解决这一问题。
下一篇我们来研究一下okhttp的重试机制。