series
OKio source code analysis
OKHttp source code parsing (1)—- overall process
OKHttp source code parsing (2) – RetryAndFollowUpInterceptor interceptor
(3)—- BridgeInterceptor
(4)—- CacheInterceptor
—- ConnectInterceptor
—- CallServerInterceptor
1. Introduction
Serves requests from the cache and writes responses to the cache.
Cache interceptor, responsible for reading the cache directly back, update the cache. If the network request has a Cache that meets the requirements, the Cache is returned directly. If the current Cache is invalid, the Cache is deleted. CacheStrategy: CacheStrategy. The CacheStrategy class is a very important class that controls whether a request is network or cached
2. Source code analysis
@override public Response Intercept (Chain Chain) throws IOException {// Obtains Response Response in the request cache cacheCandidate = cache ! = null ? cache.get(chain.request()) : null; Long now = system.currentTimemillis (); long now = system.currentTimemillis (); CacheStrategy Strategy = new cacheStrategy.factory (now, chain-.request (), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; // Trace the cache, which is actually countingif(cache ! = null) { cache.trackResponse(strategy); } // Cache does not apply and is disabledif(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, failif (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. // No network request, return cacheif (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,thenWe are doing a conditional get. // If we have both cache and networkResponse, use it according to the situationif(cacheResponse ! = null) {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(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by InitContentStream ()). / / update the original cache to cache. The latest trackConditionalCacheHit (); cache.update(cacheResponse, response);return response;
} else{ closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); // Save the previously uncached cacheif(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.
}
}
}
return response;
}
Copy the code
There are three main things to do:
- The CacheStrategy is derived from the Request and previously cached Response
- Depending on the CacheStrategy, decide whether to request the network or return the cache directly
- If you decide to request the network in 2, compare the returned network response with the local cache in this step, and add, delete, or modify the local cache
networkRequest | cacheResponse | Bear fruit |
---|---|---|
null | null | Network requests are disabled, but the cache does not exist or is out of date. 503 is returned |
null | non-null | The cache can be used to return directly to the cache without requesting the network |
non-null | null | The cache does not exist or expires. Access the network directly |
non-null | non-null | Condition get, request network |
The most important thing for a cache interceptor is the cache policy generation logic:
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
Copy the code
Generate requests and responses based on now, chain-.request, and cacheCandidate. Let’s look at the cache policy generation logic:
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); / / dateif ("Date".equalsIgnoreCase(fieldName)) { servedDate = HttpDate.parse(value); servedDateString = value; // Expiration date}else if ("Expires".equalsIgnoreCase(fieldName)) { expires = HttpDate.parse(value); // Last modified}else if ("Last-Modified".equalsIgnoreCase(fieldName)) { lastModified = HttpDate.parse(value); lastModifiedString = value; // The duration of the response object in the proxy cache, in seconds}else if ("ETag".equalsIgnoreCase(fieldName)) { etag = value; // The duration of the response object in the proxy cache, in seconds}else if ("Age".equalsIgnoreCase(fieldName)) { ageSeconds = HttpHeaders.parseSeconds(value, -1); }}}}Copy the code
Factory() : The Factory() method records some data: the current time, the body of the request, and the cached response. If the cache response has data, the cache request sending time, response receiving time, server time, validity period, last modification, and survivability time are recorded.
/**
* 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 are forbidden from using the network and the cache is insufficient.return new CacheStrategy(null, null);
}
return candidate;
}
Copy the code
Get () : The get() method gets the cache policy, the core of which is getCandidate().
/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {// No cached responseif (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// Drop the cached response ifIt is missing a required handshake. // Discard request is HTTPS and there is no handshake cacheif (request.isHttps() && cacheResponse.handshake() == null) {
returnnew CacheStrategy(request, null); } // If this response should not 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-pour and the rules are constant. // Check whether the response can be cachedif(! isCacheable(cacheResponse, request)) {return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
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; CacheControl responseCaching = cacheResponse.cacheControl();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();
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
Copy the code
summary
This chapter covers the basics of the source code, but OkHttp’s caching strategy is complex and will be examined in detail in the next chapter.
Reference
OKHTTP shares a two-cache policy