如何用?
在OkHttpClient中配置缓存的文件
Cache cache = new Cache(new File("/sdcard/test/"),100*100*1024);
OkHttpClient okHttpClient = new OkHttpClient.Builder().cache(cache).build();
Request request = new Request.Builder().url("http://www.imooc.com/courseimg/s/cover005_s.jpg").cacheControl(new CacheControl.Builder().noCache().build()).build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("info","----onFailure");
}
@Override
public void onResponse(Call call, Response response) throws IOException {
Log.e("info","----onResponse"+response.body().toString());
}
});
注意:noCache()表示不从缓存中获取Response,使用网络获取,noStore()表示服务器返回的Response不能保存,也不使用缓存。
通过Cache的构造方法我们可知okhttp的缓存是通过DiskLruCache实现。
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);
Log.i("wenfeng","new cache");
}
分析
Okhttp 已经默认对请求有设置缓存拦截,在RealCall中 添加interceptors.add(new CacheInterceptor(client.internalCache()));
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()));
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);
return chain.proceed(originalRequest);
}
client.internalCache()中返回的是一个InternalCache接口,真正的存取,获取,更新,是通过桥接模式在Cache中实现。
下面分析下实现
@Override public Response intercept(Chain chain) throws IOException {
// 根据请求Request 来获取已有的缓存
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
//当前时间
long now = System.currentTimeMillis();
//生成一个缓存策略,用来判断是否使用网络缓存,或者本地缓存,或者都用
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); // 1
//如果网络请求为空,则不使用用网络请求
Request networkRequest = strategy.networkRequest;
//如果本地缓存为空,则不使用本地缓存。
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
//记录响应次数
cache.trackResponse(strategy);
}
//如果本地缓存不为空,但是不使用缓存,则关闭
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.
//如果禁止使用网络并且本地缓存为空,则返回失败。
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); // 2
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
//如果网络返回的响应为空,但是本地缓存不为空,则关闭本地响应
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
// 如果本地缓存存在,通过本地缓存和网络返回的响应组合构建一个reponse对象
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果返回304 ,则从缓存中读取
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); //3
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
//如果本地缓存为空,则通过网络返回的响应构建一个response返回
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//判断是否包含响应体
if (HttpHeaders.hasBody(response)) {
//本地缓存响应
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
上面是这个缓存的处理过程
- 整个缓存的流程是根据缓存策略,看是否使用request服务器端的response还是使用本地的缓存,如果进行网络请求,根据缓存策略,判断是否更新本地缓存。
- 标注1 表示当前的缓存策略,如果 Request networkRequest不为null,表示进行网络请求,如果Response cacheResponse 不为null,表示进行使用本地缓存。
- 标注2 表示将责任链(也就是网络请求)分发给下一个拦截器
- 标注3 表示根据网络请求数据来更新本地缓存。
说下缓存策略也就是CacheStrategy
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
调用的地方是:
/**
* Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
*通过使用缓存响应,来返回一种策略来满足请求,
*/
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;
}
看看CacheStrategy 生成的地方,getCandidate()方法,先了解几个概念:
- “If-Modified-Since” :客户端发送给服务器的数据,用来验证当前请求的数据是不是有修改过,如果服务器的时间小于这个时间,表示服务器的时间没有修改过,客户端可以用本地的缓存数据,如果服务器的时间大于这个时间表示服务器的时间修改过,将会使用服务器的数据。
- “If-None-Match”:需要和“ETag”配合,ETag表示资源的一种标识信息,用于标识某个资源,由服务端返回,优先级更高。客户端再次向服务器端请求的时候加入字段If-None-Match(和ETag的内容相同),服务端收到请求的该字段时(之前的Etag值),和资源的唯一标识进行对比,如果相同,说明没有改动,则返回状态码304,如果不同,说明资源被改过了,则返回状态码200和整个内容数据。
private CacheStrategy getCandidate() {
// No cached response.
//如果没有本地缓存,则返回不带本地缓存的策略
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response if it's missing a required handshake.
//并且本地缓存缺少必要的握手,将删除本地缓存,则返回不带本地缓存的策略
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.
//如果本地缓存是"noStore"的,则本地缓存是不能使用的,
// 简单的说如果本地缓存和requst 设置的是"noStore"的,则返回不带本地缓存的策略,因为"nostore"是不能使用本地缓存的,也不能将请求的数据缓存到本地。
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//如果request设置不适用本地缓存或者request 有自己的请求条件时,则本地缓存是不能使用的。
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//缓存存活时间
long ageMillis = cacheResponseAge();
//获取缓存响应里声明的新鲜时间,如果没有设置则为0
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();
//mustRevalidate 如果为true ,那么maxStaleSeconds 则不用验证了,因为已经过期了,所以没有无最大过期时间。如果缓存响应不是必须要再验证,并且请求有最大过期时间,则用请求的最大过期时间作为最大过期时间
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();
//在okhttp中实现
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
//构建一个新的request
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
//构建缓存缓存策略
return new CacheStrategy(conditionalRequest, cacheResponse);
}
上面就是各种情况下,缓存策略的生成,其中涉及到一个CacheControl 这个类,这个类的主要作用是配置从服务器到客户端的缓存准则。简单的说就是控制哪些响应能被存储,哪些请求能使用这些存储的响应(也就是缓存),再简单点说就是哪些请求能使用本地缓存,以及客户端发出的请求,收到的响应,在何种情况下能本地缓存。
网友评论