源码解析
@Override public Response intercept(Chain chain) throws IOException {
//默认cache为null,可以配置cache,不为空尝试获取缓存中的response
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
//根据response,time,request创建一个缓存策略,用于判断怎样使用缓存
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
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.
//如果缓存策略中禁止使用网络,并且缓存又为空,则构建一个Resposne直接返回,注意返回码=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();
}
// 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);
} 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.
//当缓存响应和网络响应同时存在的时候,选择用哪个
if (cacheResponse != null) {
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//如果返回码是304,客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户
// 只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
//则使用缓存的响应
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();
//所以默认创建的OkHttpClient是没有缓存的
if (cache != null) {
//将响应缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//缓存Resposne的Header信息
CacheRequest cacheRequest = cache.put(response);
//缓存body
return cacheWritingResponse(cacheRequest, response);
}
//只能缓存GET....不然移除request
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
- 没有缓存和禁止使用网络 直接返回504
- 有缓存禁止使用网络直接返回缓存
- 请求网络,如果后台内容没有改变,返回缓存
- 后台内容改变缓存后台返回的内容,并返回。
Cache.java
CacheRequest put(Response response) {
String requestMethod = response.request().method();
//判断请求如果是"POST"、"PATCH"、"PUT"、"DELETE"、"MOVE"中的任何一个则调用DiskLruCache.remove(urlToKey(request));将这个请求从缓存中移除出去。
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//判断请求如果不是Get则不进行缓存,直接返回null。官方给的解释是缓存get方法得到的Response效率高,其它方法的Response没有缓存效率低。通常通过get方法获取到的数据都是固定不变的的,因此缓存效率自然就高了。其它方法会根据请求报文参数的不同得到不同的Response,因此缓存效率自然而然就低了。
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;
}
//判断请求中的http数据包中headers是否有符号"*"的通配符,有则不缓存直接返回null
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
//由Response对象构建一个Entry对象,Entry是Cache的一个内部类
Entry entry = new Entry(response);
//通过调用DiskLruCache.edit();方法得到一个DiskLruCache.Editor对象。
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
//把这个entry写入
//方法内部是通过Okio.buffer(editor.newSink(ENTRY_METADATA));获取到一个BufferedSink对象,随后将Entry中存储的Http报头数据写入到sink流中。
entry.writeTo(editor);
//构建一个CacheRequestImpl对象,构造器中通过editor.newSink(ENTRY_BODY)方法获得Sink对象
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
通过DiskLruCache.Edito写入new的Entry。
remove
void remove(Request request) throws IOException {
cache.remove(key(request.url()));
}
public static String key(HttpUrl url) {
return ByteString.encodeUtf8(url.toString()).md5().hex();
}
update
void update(Response cached, Response network) {
//用response构造一个Entry对象
Entry entry = new Entry(network);
//从命中缓存中获取到的DiskLruCache.Snapshot
DiskLruCache.Snapshot snapshot = ((CacheResponseBody) cached.body()).snapshot;
//从DiskLruCache.Snapshot获取DiskLruCache.Editor()对象
DiskLruCache.Editor editor = null;
try {
editor = snapshot.edit(); // Returns null if snapshot is not current.
if (editor != null) {
//将entry写入editor中
entry.writeTo(editor);
editor.commit();
}
} catch (IOException e) {
abortQuietly(editor);
}
}
get
Response get(Request request) {
//获取url经过MD5和HEX的key
String key = key(request.url());
DiskLruCache.Snapshot snapshot;
Entry entry;
try {
//根据key来获取一个snapshot,由此可知我们的key-value里面的value对应的是snapshot
snapshot = cache.get(key);
if (snapshot == null) {
return null;
}
} catch (IOException e) {
// Give up because the cache cannot be read.
return null;
}
//利用前面的Snapshot创建一个Entry对象。存储的内容是响应的Http数据包Header部分的数据。snapshot.getSource得到的是一个Source对象 (source是okio里面的一个接口)
try {
entry = new Entry(snapshot.getSource(ENTRY_METADATA));
} catch (IOException e) {
Util.closeQuietly(snapshot);
return null;
}
//利用entry和snapshot得到Response对象,该方法内部会利用前面的Entry和Snapshot得到响应的Http数据包Body(body的获取方式通过snapshot.getSource(ENTRY_BODY)得到)创建一个CacheResponseBody对象;再利用该CacheResponseBody对象和第三步得到的Entry对象构建一个Response的对象,这样该对象就包含了一个网络响应的全部数据了。
Response response = entry.response(snapshot);
//对request和Response进行比配检查,成功则返回该Response。匹配方法就是url.equals(request.url().toString()) && requestMethod.equals(request.method()) && OkHeaders.varyMatches(response, varyHeaders, request);其中Entry.url和Entry.requestMethod两个值在构建的时候就被初始化好了,初始化值从命中的缓存中获取。因此该匹配方法就是将缓存的请求url和请求方法跟新的客户请求进行对比。最后OkHeaders.varyMatches(response, varyHeaders, request)是检查命中的缓存Http报头跟新的客户请求的Http报头中的键值对是否一样。如果全部结果为真,则返回命中的Response。
if (!entry.matches(request, response)) {
Util.closeQuietly(response.body());
return null;
}
return response;
}