CacheInterceptor
介紹完快取之後,現在開始介紹快取攔截器CacheInterceptor了,同樣也是檢視其intercept()方法,這裡邊上片段程式碼邊解析,化整為零:
@Override public Response intercept(Chain chain) throws IOException {
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
...
}
複製程式碼
首先通過判斷快取物件是否為null,如果不為null則根據傳入的Chain物件的request獲取快取的Response。
@Override public Response intercept(Chain chain) throws IOException {
...
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
...
}
複製程式碼
建立快取策略物件CacheStrategy,CacheStrategy內部維護了一個Request和一個Response,該類主要是用於解決到底使用網路還是快取,亦或是兩者皆用:
/** 如果不使用網路,則 networkRequest為 null */
public final @Nullable Request networkRequest;
/** 如果不使用快取,則 cacheResponse為 null */
public final @Nullable Response cacheResponse;
複製程式碼
關於快取策略的建立,我們檢視CacheStrategy的內部類Factory的get()方法:
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()方法中:
private CacheStrategy getCandidate() {
//找不到快取,需要網路請求
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 如果是https請求且缺少握手操作,需要網路請求
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 判斷網路請求該不該快取下來,不該快取則,需要網路請求
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
// 如果指定不快取或者是可選擇的請求,需要網路請求
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//上述需要網路請求返回的 CacheStrategy 第一個引數傳入request
//如果快取是不受影響的,CacheStrategy傳入cacheResponse
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
//可以快取,新增請求頭資訊
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());
}
...
}
複製程式碼
介紹完CacheStrategy物件和建立CacheStrategy的過程後,接著分析intercept()方法:
@Override public Response intercept(Chain chain) throws IOException {
...
//如果有快取,更新統計指標, 增加命中率
if (cache != null) {
cache.trackResponse(strategy);
}
...
//如果當前沒有網路且找不到快取
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();
}
...
}
複製程式碼
如果有快取,通過呼叫CacheStrategy的trackResponse()方法更新統計指標, 更新網路請求數和命中快取數;
接著通過判斷CacheStrategy的networkRequest和cacheResponse,如果二者同時為null,即當前沒有網路且沒有快取,則構造一個Response物件並返回,其中狀態碼為504,並設定了提示的message資訊。
接著分析:
@Override public Response intercept(Chain chain) throws IOException {
...
//如果不使用網路請求,直接返回快取的Response
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 (cacheResponse != null) {
//伺服器返回狀態碼為HTTP_NOT_MODIFIED(304)
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
//使用快取資料
Response response = cacheResponse.newBuilder()
...
.build();
...
} else {
closeQuietly(cacheResponse.body());
}
}
//如果伺服器資源已經修改,使用網路響應返回的最新資料
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//如果有快取
if (cache != null) {
//htpp 頭部有響應體且需要快取
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
//請求方法不符合能夠快取
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
//移除對應的request
cache.remove(networkRequest);
} catch (IOException ignored) {
}
}
}
return response;
}
複製程式碼
當伺服器返回狀態碼為HTTP_NOT_MODIFIED即304時,說明快取還沒過期或伺服器資源沒修改,此時返回快取;如果伺服器資源修改了,則使用網路響應返回的最新資料構造Response,接著將最新的資料快取並移除無效的快取。
總結CacheInterceptor主要做的操作:
- 從快取中獲取Reponse物件,賦值給caceResponse,如果找不到快取則為null;
- 根據當前請求request和caceResponse 構建一個CacheStrategy物件;
- CacheStrategy這個策略物件將根據相關規則來決定cacheResponse和Request是否有效,如果無效則分別將cacheResponse和request設定為null;
- 如果request和cacheResponse都為null,即沒有網路且沒有快取,直接返回一個狀態碼為504的空Respone物件;
- 如果resquest為null而cacheResponse不為null,即沒有網路且有快取,則直接返回cacheResponse物件;
- 執行下一個攔截器的intercept()方法進行網路請求,獲取response;
- 如果伺服器資源沒有過期(狀態碼304)且存在快取,則返回快取;
- 如果伺服器資源有修改,則將返回的最新資料進行快取並移除掉無效的快取,最後返回response物件給上一個攔截器。
下一篇將講解五大攔截器中的最後兩個攔截器, ConnectInterceptor和CallServerInterceptor,感興趣的朋友可以繼續閱讀:
OkHttpClient原始碼分析(五)—— ConnectInterceptor和CallServerInterceptor