okhttp之旅(十一)--快取策略
系統學習詳見OKhttp原始碼解析詳解系列
1 HTTP與快取相關的理論知識,這是實現Okhttp機制的基礎。
- HTTP的快取機制也是依賴於請求和響應header裡的引數類實現的,最終響應式從快取中去,還是從服務端重新拉取,HTTP的快取機制的流程如下所示:
2 HTTP的快取可以分為兩種:
- 強制快取:需要服務端參與判斷是否繼續使用快取,當客戶端第一次請求資料是,服務端返回了快取的過期時間(Expires與Cache-Control),沒有過期就可以繼續使用快取,否則則不適用,無需再向服務端詢問。
- 對比快取:需要服務端參與判斷是否繼續使用快取,當客戶端第一次請求資料時,服務端會將快取標識(Last-Modified/If-Modified-Since與Etag/If-None-Match)與資料一起返回給客戶端,客戶端將兩者都備份到快取中 ,再次請求資料時,客戶端將上次備份的快取
標識傳送給服務端,服務端根據快取標識進行判斷,如果返回304,則表示通知客戶端可以繼續使用快取。- 強制快取優先於對比快取。
3 強制快取使用的的兩個標識:
- Expires:Expires的值為服務端返回的到期時間,即下一次請求時,請求時間小於服務端返回的到期時間,直接使用快取資料。到期時間是服務端生成的,客戶端和服務端的時間可能有誤差。
- Cache-Control:Expires有個時間校驗的問題,所有HTTP1.1採用Cache-Control替代Expires。
- Cache-Control的取值有以下幾種:
- private: 客戶端可以快取。
- public: 客戶端和代理伺服器都可快取。
- max-age=xxx: 快取的內容將在 xxx 秒後失效
- no-cache: 需要使用對比快取來驗證快取資料。
- no-store: 所有內容都不會快取,強制快取,對比快取都不會觸發。
4 對比快取的兩個標識:
4.1 時間戳標記資源是否修改的方法
- Last-Modified 表示資源上次修改的時間。
當客戶端傳送第一次請求時,服務端返回資源上次修改的時間:
Last-Modified: Tue, 12 Jan 2016 09:31:27 GMT
客戶端再次傳送,會在header裡攜帶If-Modified-Since。將上次服務端返回的資源時間上傳給服務端。
If-Modified-Since: Tue, 12 Jan 2016 09:31:27 GMT
- 服務端接收到客戶端發來的資源修改時間,與自己當前的資源修改時間進行對比,如果自己的資源修改時間大於客戶端發來的資源修改時間,則說明資源做過修改, 則返回200表示需要重新請求資源,否則返回304表示資源沒有被修改,可以繼續使用快取。
4.2 資源標識碼ETag的方式來標記是否修改
如果標識碼發生改變,則說明資源已經被修改,ETag優先順序高於Last-Modified。
- ETag是資原始檔的一種標識碼,當客戶端傳送第一次請求時,服務端會返回當前資源的標識碼:
ETag: "5694c7ef-24dc"
- 客戶端再次傳送,會在header裡攜帶上次服務端返回的資源標識碼:
If-None-Match:"5694c7ef-24dc"
- 服務端接收到客戶端發來的資源標識碼,則會與自己當前的資源嗎進行比較,如果不同,則說明資源已經被修改,則返回200,如果相同則說明資源沒有被修改,返回 304,客戶端可以繼續使用快取。
2 HTTP快取策略
Okhttp的快取策略就是根據上述流程圖實現的。具體的實現類是CacheStrategy。
2.1 CacheStrategy的建構函式
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
- 這兩個引數引數的含義如下:
- networkRequest:網路請求。
- cacheResponse:快取響應,基於DiskLruCache實現的檔案快取,可以是請求中url的md5,value是檔案中查詢到的快取。
- CacheStrategy就是利用這兩個引數生成最終的策略,有點像map操作,將networkRequest與cacheResponse這兩個值輸入,處理之後再將這兩個值輸出,們的組合結果如下所示:
- 如果networkRequest為null,cacheResponse為null:only-if-cached(表明不進行網路請求,且快取不存在或者過期,一定會返回503錯誤)。
- 如果networkRequest為null,cacheResponse為non-null:不進行網路請求,而且快取可以使用,直接返回快取,不用請求網路。
- 如果networkRequest為non-null,cacheResponse為null:需要進行網路請求,而且快取不存在或者過期,直接訪問網路。
- 如果networkRequest為non-null,cacheResponse為non-null:Header中含有ETag/Last-Modified標籤,需要在條件請求下使用,還是需要訪問網路。
2.2 四種情況的判定
- CacheStrategy是利用Factory模式進行構造的
- CacheStrategy.Factory物件構建以後,呼叫它的get()方法即可獲得具體的CacheStrategy
- CacheStrategy.Factory.get()方法內部 呼叫的是CacheStrategy.Factory.getCandidate()方法,它是核心的實現。
- 整個函式的邏輯就是按照上面那個HTTP快取判定流程圖來實現,具體流程如下所示:
- 1.如果快取沒有命中,就直接進行網路請求。
- 2.如果TLS握手資訊丟失,則返回直接進行連線。
- 3.根據response狀態碼,Expired時間和是否有no-cache標籤就行判斷是否進行直接訪問。
- 4.如果請求header裡有"no-cache"或者右條件GET請求(header裡帶有ETag/Since標籤),則直接連線。
- 5.如果快取在過期時間內則可以直接使用,則直接返回上次快取。
- 6.如果快取過期,且有ETag等資訊,則傳送If-None-Match、If-Modified-Since、If-Modified-Since等條件請求交給服務端判斷處理
- Okhttp的快取是根據伺服器header自動的完成的,整個流程也是根據RFC文件寫死的,客戶端不必要進行手動控制。
public static class Factory {
private CacheStrategy getCandidate() {
//1. 如果快取沒有命中,就直接進行網路請求。
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//2. 如果TLS握手資訊丟失,則返回直接進行連線。
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
//3. 根據response狀態碼,Expired時間和是否有no-cache標籤就行判斷是否進行直接訪問。
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//4. 如果請求header裡有"no-cache"或者右條件GET請求(header裡帶有ETag/Since標籤),則直接連線。
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
//計算當前age的時間戳:now - sent + age
long ageMillis = cacheResponseAge();
//重新整理時間,一般伺服器設定為max-age
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
//一般取max-age
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
//一般取0
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//5. 如果快取在過期時間內則可以直接使用,則直接返回上次快取。
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());
}
//6. 如果快取過期,且有ETag等資訊,則傳送If-None-Match、If-Modified-Since、If-Modified-Since等條件請求
//交給服務端判斷處理
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);
}
}
3 快取管理
- 快取機制是基於DiskLruCache做的。Cache類封裝了快取的實現,實現了InternalCache介面。
InternalCache介面如下所示:
public interface InternalCache {
//獲取快取
Response get(Request request) throws IOException;
//存入快取
CacheRequest put(Response response) throws IOException;
//移除快取
void remove(Request request) throws IOException;
//更新快取
void update(Response cached, Response network);
//跟蹤一個滿足快取條件的GET請求
void trackConditionalCacheHit();
//跟蹤滿足快取策略CacheStrategy的響應
void trackResponse(CacheStrategy cacheStrategy);
}
Cache
- Cache沒有直接實現InternalCache這個介面,而是在其內部實現了InternalCache的匿名內部類,內部類的方法呼叫Cache對應的方法,如下所示:
final InternalCache internalCache = new InternalCache() {
@Override public Response get(Request request) throws IOException {
return Cache.this.get(request);
}
@Override public CacheRequest put(Response response) throws IOException {
return Cache.this.put(response);
}
@Override public void remove(Request request) throws IOException {
Cache.this.remove(request);
}
@Override public void update(Response cached, Response network) {
Cache.this.update(cached, network);
}
@Override public void trackConditionalCacheHit() {
Cache.this.trackConditionalCacheHit();
}
@Override public void trackResponse(CacheStrategy cacheStrategy) {
Cache.this.trackResponse(cacheStrategy);
}
};
InternalCache internalCache() {
return cache != null ? cache.internalCache : internalCache;
}
Cache類裡還定義一些內部類
- Cache.Entry:封裝了請求與響應等資訊,包括url、varyHeaders、protocol、code、message、responseHeaders、handshake、sentRequestMillis與receivedResponseMillis。
- Cache.CacheResponseBody:繼承於ResponseBody,封裝了快取快照snapshot,響應體bodySource,內容型別contentType,內容長度contentLength。
- Okhttp還封裝了一個檔案系統類FileSystem類,這個類利用Okio這個庫對Java的FIle操作進行了一層封裝,簡化了IO操作。理解了這些剩下的就是DiskLruCahe裡的插入快取 、獲取快取和刪除快取的操作。
相關文章
- OkHttp3.0解析——談談內部的快取策略HTTP快取
- okhttp 快取實踐HTTP快取
- 快取策略快取
- OkHttp設定支援Etag快取HTTP快取
- OkHttp 知識梳理(4) - OkHttp 之快取原始碼解析HTTP快取原始碼
- HTTP - 快取策略HTTP快取
- Web 快取機制 與 快取策略Web快取
- OkHttp 原始碼分析(二)—— 快取機制HTTP原始碼快取
- 你真的瞭解 OkHttp 快取控制嗎?HTTP快取
- Flutter 的快取策略Flutter快取
- SDWebImage的快取策略Web快取
- http快取策略以及強快取和協商快取淺析HTTP快取
- OkHttp 原始碼剖析系列(三)——快取機制HTTP原始碼快取
- Retrofit和OkHttp實現 Android網路快取HTTPAndroid快取
- 瀏覽器快取策略瀏覽器快取
- RN的快取策略探索快取
- Java Integer的快取策略Java快取
- Redis篇:持久化、淘汰策略,快取失效策略Redis持久化快取
- Web 專案的快取策略Web快取
- 輕鬆理解HTTP快取策略HTTP快取
- AFNetworking和YTKNetwork的快取策略快取
- 秒懂前端的快取策略前端快取
- PWA常見的快取策略快取
- 快取策略之瀏覽器快取瀏覽器
- [Android]Okhttp心跳策略研究AndroidHTTP
- 深度詳解GaussDB bufferpool快取策略快取
- 深入剖析瀏覽器快取策略瀏覽器快取
- SpringBoot 實戰 (十一) | 整合資料快取 CacheSpring Boot快取
- (五)Redis 快取異常、應對策略Redis快取
- 我理解的瀏覽器快取策略瀏覽器快取
- 徹底弄懂瀏覽器快取策略瀏覽器快取
- 前端效能優化之HTTP快取策略前端優化HTTP快取
- 快取更新的四種策略及選取建議快取
- Redis-6-三種快取讀寫策略Redis快取
- 微服務複雜查詢之快取策略微服務快取
- 雲伺服器:Apache快取策略設定伺服器Apache快取
- 前端網路程式設計之快取策略前端程式設計快取
- 簡易筆記:瀏覽器快取策略筆記瀏覽器快取