okhhtp緩存篇_上#
注:本文分析的基礎(chǔ)是在大概了解了okhhtp的基礎(chǔ)上分析的如果不了解的話建議看下okhhtp的網(wǎng)絡(luò)流程https://blog.csdn.net/wkk_ly/article/details/81004920
前言:okhttp_緩存篇分為上下兩部分,主要從以下幾個(gè)方面來(lái)分析okhhtp緩存
- okhttp緩存如何聲明使用緩存
- okhttp在什么情況下緩存網(wǎng)絡(luò)響應(yīng)(這個(gè)涉及我們更好的使用okhttp緩存)
- okhhtp在什么情況下使用緩存而不是使用網(wǎng)絡(luò)請(qǐng)求(這個(gè)涉及我們更好的使用okhttp緩存)
- okhhtp如何存儲(chǔ)緩存的
- okhhtp是如何取出緩存的
- 總結(jié)okhhtp的緩存&&使用
- 附錄cache請(qǐng)求(響應(yīng))頭的各個(gè)字段及其含義
上篇分析 1,2,3,
下篇分析 4,5,6,7
如何使用okhhtp緩存
首先還是看下官方實(shí)例:
private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
int cacheSize = 10 * 1024 * 1024; // 10 MiB
//需要指定一個(gè)私有目錄,以及緩存限制的大小,官方給出的限制是10MB
Cache cache = new Cache(cacheDirectory, cacheSize);
client = new OkHttpClient.Builder()
.cache(cache)
.build();
}
嗯 okhttp實(shí)現(xiàn)緩存配置就是你需要在OkHttpClient配置中設(shè)置緩存,如上就是設(shè)置一個(gè)cache 這個(gè)cache類需要指定一個(gè)目錄以及一個(gè)緩存的文件大小,但是有個(gè)要求就是:
這個(gè)緩存目錄要是私有的,而且這個(gè)緩存目錄只能是單個(gè)緩存訪問(wèn)否則就會(huì)異常,我理解的就是只能是一個(gè)應(yīng)用訪問(wèn)或者說(shuō)是只能是一個(gè)具有相同配置的OkHttpClient訪問(wèn),
研究緩存前的準(zhǔn)備
下面我們來(lái)看看okhttp內(nèi)部是如何實(shí)現(xiàn)緩存的,因?yàn)閛khttp是責(zé)任鏈模式,所以我們直接看okhttp的緩存攔截器,即:CacheInterceptor就可以分析它的緩存實(shí)現(xiàn)
注:如果不了解.okhttp的責(zé)任鏈可以看下我的上篇文章https://blog.csdn.net/wkk_ly/article/details/81004920
不過(guò)我們需要先做一點(diǎn)準(zhǔn)備工作:我們要看下緩存攔截器是如何設(shè)置的(換句話說(shuō)是如何添加進(jìn)攔截鏈的)
在RealCall類的getResponseWithInterceptorChain()是設(shè)置攔截鏈并獲取網(wǎng)絡(luò)響應(yīng)的方法:
Response getResponseWithInterceptorChain() throws IOException {
List<Interceptor> interceptors = new ArrayList<>();
....
//這行代碼是設(shè)置緩存攔截器,
interceptors.add(new CacheInterceptor(client.internalCache()));
....
}
ok我們?cè)谶@里知道了緩存攔截器的設(shè)置,那我們?cè)賮?lái)看看client的internalCache()方法是什么,點(diǎn)進(jìn)來(lái)后是下面的內(nèi)容
InternalCache internalCache() {
//這個(gè)cache記得我們使用okhttp緩存時(shí)設(shè)置的代碼cache(cache)嗎 對(duì)這個(gè)cache就是我們?cè)O(shè)置的cache,也就是說(shuō)我們需要傳入的cache的內(nèi)部實(shí)現(xiàn)接口internalCache
//當(dāng)我們沒(méi)有設(shè)置cache是就會(huì)取默認(rèn)的internalCache,那這個(gè)internalCache是什么呢,實(shí)際上okhttp.builder有如下的方法
// void setInternalCache(@Nullable InternalCache internalCache) {
// this.internalCache = internalCache;
// this.cache = null;
//}
//InternalCache是一個(gè)實(shí)現(xiàn)緩存策略的接口,就是說(shuō)我們可以自定義緩存實(shí)現(xiàn)方法,只要實(shí)現(xiàn)InternalCache接口并設(shè)置進(jìn)來(lái)就可以了,但是我們本文的主題是分析okhhtp的緩存策略,
//所以我們?cè)谶@不在分析自定義的緩存策略
return cache != null ? cache.internalCache : internalCache;
}
好了我們現(xiàn)在知道了,緩存攔截器需要初始化的時(shí)候穿進(jìn)去換一個(gè)緩存實(shí)現(xiàn)策略,而這個(gè)策略就是我們?cè)谠O(shè)置okhhtp緩存時(shí)設(shè)置進(jìn)去的,下面粘貼下緩存策略接口InternalCache,注意我們?cè)谥暗脑O(shè)置的Cache類內(nèi)部是實(shí)現(xiàn)了該接口的(他本是并沒(méi)有實(shí)現(xiàn)該接口,而是在內(nèi)部聲明了一個(gè)內(nèi)部類實(shí)現(xiàn)了該方法),關(guān)于Cache類的源碼在此先不粘貼了 我們后面會(huì)進(jìn)行研究討論,
public interface InternalCache {
//根據(jù)請(qǐng)求獲取緩存響應(yīng)
Response get(Request request) throws IOException;
//存儲(chǔ)網(wǎng)絡(luò)響應(yīng),并將原始的請(qǐng)求處理返回緩存請(qǐng)求
CacheRequest put(Response response) throws IOException;
/**
* Remove any cache entries for the supplied {@code request}. This is invoked when the client
* invalidates the cache, such as when making POST requests.
*/
//當(dāng)緩存無(wú)效的時(shí)候移除緩存(),特別是不支持的網(wǎng)絡(luò)請(qǐng)求方法,okhhtp只支持緩存get請(qǐng)求方法,其他的都不支持,之后我們會(huì)在代碼中看到的
void remove(Request request) throws IOException;
/**
* Handles a conditional request hit by updating the stored cache response with the headers from
* {@code network}. The cached response body is not updated. If the stored response has changed
* since {@code cached} was returned, this does nothing.
*/
void update(Response cached, Response network);
/** Track an conditional GET that was satisfied by this cache. */
void trackConditionalCacheHit();
/** Track an HTTP response being satisfied with {@code cacheStrategy}. */
void trackResponse(CacheStrategy cacheStrategy);
}
okhttp在什么情況下緩存網(wǎng)絡(luò)響應(yīng)
我們這一小節(jié)的標(biāo)題是分析存儲(chǔ)緩存 所以我們忽略其他的部分,我們首先默認(rèn)我們?cè)O(shè)置了緩存策略但是此時(shí)沒(méi)有任何緩存存儲(chǔ),這樣可以更方便我們分析
那我們直接看緩存攔截器的 攔截方法即是intercept(Chain chain)方法
@Override public Response intercept(Chain chain) throws IOException {
.....
Response networkResponse = null;
try {
//這里獲取網(wǎng)絡(luò)響應(yīng)
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());
}
}
...
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
//判斷是否有緩存策略 我們默認(rèn)是設(shè)置了緩存策略,所此時(shí)為true,
if (cache != null) {
//這個(gè)判斷是是否有響應(yīng)體,我們首先假設(shè)是正常的請(qǐng)求并且獲得正常的響應(yīng)(關(guān)于如何判斷是否有響應(yīng)體,我們稍后研究),所以我們這里主要研究 CacheStrategy.isCacheable(response, networkRequest)
//這個(gè)是判斷是否緩存的主要依據(jù)
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
//調(diào)用緩存策略,緩存網(wǎng)絡(luò)響應(yīng)
CacheRequest cacheRequest = cache.put(response);
//返回響應(yīng),這個(gè)我們?cè)谙旅娣治? return cacheWritingResponse(cacheRequest, response);
}
//這里是如果是不支持的網(wǎng)絡(luò)請(qǐng)求方法,則刪除緩存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
在上面的代碼中 我們知道了如果要緩存網(wǎng)絡(luò)請(qǐng)求主要是有3個(gè)依據(jù)
1.要有緩存策略(可以是自定義的,也可以是用okhttp提供的Cache),否則不緩存
2,要有響應(yīng)體,否則不緩存
3,要滿足緩存條件否則不緩存,
第一個(gè)條件我們都知道,必須設(shè)置緩存策略否則肯定不緩存的,那我們重點(diǎn)來(lái)看下面這兩個(gè)條件
要有響應(yīng)體否則不緩存
字面意思當(dāng)然是很好理解了,沒(méi)有網(wǎng)絡(luò)響應(yīng)體,的確沒(méi)有響應(yīng)體自然沒(méi)有緩存的必要了 不過(guò)我們還是要看下這個(gè)判斷的(雖然我認(rèn)為沒(méi)什么必要),我們看下這個(gè)方法
public static boolean hasBody(Response response) {
// HEAD requests never yield a body regardless of the response headers.
//如果網(wǎng)絡(luò)請(qǐng)求的方法是head方法則返回false,
//Head方法 在服務(wù)器的響應(yīng)中只返回響應(yīng)頭,不會(huì)返回響應(yīng)體
if (response.request().method().equals("HEAD")) {
return false;
}
int responseCode = response.code();
//HTTP_CONTINUE是100 HTTP_NO_CONTENT是204 HTTP_NOT_MODIFIED是304
//解釋看下面(*)
if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
&& responseCode != HTTP_NO_CONTENT
&& responseCode != HTTP_NOT_MODIFIED) {
return true;
}
// If the Content-Length or Transfer-Encoding headers disagree with the response code, the
// response is malformed. For best compatibility, we honor the headers.
//如果響應(yīng)頭的Content-Length和Transfer-Encoding字段和返回狀態(tài)碼不一致的時(shí)候 按照響應(yīng)頭為準(zhǔn)
//即是如果響應(yīng)嗎不在上述的要求內(nèi),但是響應(yīng)頭又符合又響應(yīng)體的要求則返回true
//注:Content-Length:這是表示響應(yīng)體的長(zhǎng)度,contentLength(response)也就是獲取該字段的值
//Transfer-Encoding:分塊傳輸 就是將響應(yīng)體分成多個(gè)塊進(jìn)行傳輸(這個(gè)也就代表著肯定有響應(yīng)體,關(guān)于詳細(xì)描述見下面頭部字段附錄)
if (contentLength(response) != -1
|| "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
return true;
}
return false;
}
(*) 我們首先大概描述下http各個(gè)響應(yīng)碼
- 1XX: 100-199 的狀態(tài)碼代表著信息性狀態(tài)碼
- 2xx: 200-299 的狀態(tài)碼代表著成功狀態(tài)碼,但是204狀態(tài)碼代表著沒(méi)有實(shí)體僅僅有響應(yīng)行和響應(yīng)頭
- 3xx: 300-399 的狀態(tài)碼代表著重定向 但是304狀態(tài)碼代表著請(qǐng)求資源未修改,請(qǐng)使用緩存,只有響應(yīng)行和響應(yīng)頭沒(méi)有響應(yīng)體
- 4xx: 400-499 的狀態(tài)碼代表著客戶端錯(cuò)誤代碼
- 5xx: 500-599 的狀態(tài)碼代表著服務(wù)器錯(cuò)誤代碼
這樣我們也就可以理解上面的判斷了, 響應(yīng)嗎<100或者響應(yīng)碼>=200,但是響應(yīng)碼!=204而且響應(yīng)碼!=304,
首先 http的響應(yīng)碼是沒(méi)有100以下 關(guān)于小于100的判斷是什么作用 暫時(shí)沒(méi)有搞懂 不過(guò)這個(gè)不影響
我們看完了根據(jù)響應(yīng)碼判斷是否有響應(yīng)體的判斷,我們接下來(lái)看是否符合緩存要求的判斷CacheStrategy.isCacheable(response, networkRequest),這個(gè)算是我們?cè)撔《蔚闹攸c(diǎn)內(nèi)容了,
如下為isCacheable()方法:判斷是否符合緩存要求
public static boolean isCacheable(Response response, Request request) {
// Always go to network for uncacheable response codes (RFC 7231 section 6.1),
// This implementation doesn't support caching partial content.
switch (response.code()) {
case HTTP_OK://200 成功返回狀態(tài)碼
case HTTP_NOT_AUTHORITATIVE://203 實(shí)體首部包含的信息來(lái)至與資源源服務(wù)器的副本而不是來(lái)至與源服務(wù)器
case HTTP_NO_CONTENT://204 只有首部沒(méi)有實(shí)體,但是還記得根據(jù)響應(yīng)碼判斷是否有響應(yīng)體的判斷嗎 那時(shí)已經(jīng)排除了204 和304的狀態(tài)碼
case HTTP_MULT_CHOICE://300
case HTTP_MOVED_PERM://301
case HTTP_NOT_FOUND://404 找不到資源
case HTTP_BAD_METHOD://405 請(qǐng)求方法不支持
case HTTP_GONE://410 和404類似 只是服務(wù)器以前擁有該資源但是刪除了
case HTTP_REQ_TOO_LONG://414 客戶端發(fā)出的url長(zhǎng)度太長(zhǎng)
case HTTP_NOT_IMPLEMENTED://501 服務(wù)器發(fā)生一個(gè)錯(cuò)誤 無(wú)法提供服務(wù)
case StatusLine.HTTP_PERM_REDIRECT://308
// These codes can be cached unless headers forbid it.
//以上的狀態(tài)碼都可以被緩存除非 首部不允許
break;
case HTTP_MOVED_TEMP://302 請(qǐng)求的url已被移除,
case StatusLine.HTTP_TEMP_REDIRECT://307 和302類似
// These codes can only be cached with the right response headers.
// http://tools.ietf.org/html/rfc7234#section-3
// s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
//這些響應(yīng)碼也可以被緩存但是對(duì)應(yīng)的頭部 包含以下條件 需要包含 Expires(資源過(guò)期字段) 字段 且不為空
//或者cacheControl的maxAge
if (response.header("Expires") != null
|| response.cacheControl().maxAgeSeconds() != -1
|| response.cacheControl().isPublic()
|| response.cacheControl().isPrivate()) {
break;
}
// Fall-through.
//注意此處 默認(rèn)返回false 也就是說(shuō)只有符合要求的狀態(tài)碼才可以被緩存 ,這個(gè)也就解決了上面我們提出的那個(gè)問(wèn)題
default:
// All other codes cannot be cached.
return false;
}
// A 'no-store' directive on request or response prevents the response from being cached.
return !response.cacheControl().noStore() && !request.cacheControl().noStore();
}
為了更好的理解上面的代碼 我們先說(shuō)下上面用到頭部字段
下圖是我用Fiddler隨便抓取的一個(gè)網(wǎng)絡(luò)請(qǐng)求包,我們看紅色框中的幾個(gè)字段
Expires 指的是請(qǐng)求資源的過(guò)期時(shí)間源于http1.0
Cache-Control :這個(gè)是目前在做緩存時(shí)最重要的一個(gè)字段,用來(lái)指定緩存規(guī)則,他有很多的值,不同的值代表著不同的意義,而且這個(gè)值是可以組合的 如下圖
說(shuō)下 Cache-Control各個(gè)字段的不同含義(首部字段不區(qū)分大小寫):
- no-cache :在使用該請(qǐng)求緩存時(shí)必須要先到服務(wù)器驗(yàn)證 寫法舉例 Cache-Control:no-cache
- no-store :不緩存該請(qǐng)求,如果緩存了必須刪除緩存 寫法舉例 Cache-Control:no-store
- max-age :該資源有效期的最大時(shí)間 寫法舉例 Cache-Control:max-age=3600
- s-maxage :和max-age 類似 不過(guò)該字段是用于共享(公共)緩存 寫法舉例 Cache-Control:s-maxage=3600
- private :表明響應(yīng)只可以被單個(gè)用戶緩存 不能被代理緩存 寫法舉例 Cache-Control:private
- public :表明響應(yīng)可以被任何用戶緩存 是共享(例如 客戶端 代理服務(wù)器等等) 寫法舉例 Cache-Control:public
- must-revalidate:緩存必須在使用之前驗(yàn)證舊資源的狀態(tài),并且不可使用過(guò)期資源。 寫法舉例 Cache-Control:must-revalidate
- max-stale:表明緩存在資源過(guò)期后的max-stale指定的時(shí)間內(nèi)還可以繼續(xù)使用,但是超過(guò)這個(gè)時(shí)間就必須請(qǐng)求服務(wù)器 寫法舉例 Cache-Control:max-stale(代表著資源永不過(guò)期) Cache-Control:max-stale=3600(表明在緩存過(guò)期后的3600秒內(nèi)還可以繼續(xù)用)
- min-fresh:最小要保留的新鮮度 ,即是假如緩存設(shè)置的最大新鮮時(shí)間為max-age=500 最小新鮮度為min-fresh=300 則該緩存的真正的新鮮時(shí)間 是max-age-min-fresh=200 也就是說(shuō)緩存在200秒內(nèi)有效 超過(guò)200秒就必須要請(qǐng)求服務(wù)器驗(yàn)證
- only-if-cached:如果緩存存在就使用緩存 無(wú)論服務(wù)器是否更新(無(wú)論緩存是否過(guò)期) 寫法舉例 Cache-Control:only-if-cached
- no-transform:不得對(duì)資源進(jìn)行轉(zhuǎn)換或轉(zhuǎn)變。Content-Encoding, Content-Range, Content-Type等HTTP頭不能由代理修改。例如,非透明代理可以對(duì)圖像格式進(jìn)行轉(zhuǎn)換,以便節(jié)省緩存空間或者減少緩慢鏈路上的流量。 no-transform指令不允許這樣做。 寫法舉例 Cache-Control:no-transform
- immutable:表示響應(yīng)正文不會(huì)隨時(shí)間而改變。資源(如果未過(guò)期)在服務(wù)器上不發(fā)生改變,因此客戶端不應(yīng)發(fā)送重新驗(yàn)證請(qǐng)求頭(例如If-None-Match或If-Modified-Since)來(lái)檢查更新,即使用戶顯式地刷新頁(yè)面 寫法舉例 Cache-Control:immutable
好了 看完http對(duì)Cache-Control 字段的說(shuō)明 我們?cè)賮?lái)看下面的代碼(CacheInterceptor類中intercept方法)應(yīng)該大概可以猜測(cè)到時(shí)是什么意思了,先說(shuō)下 這里的response是服務(wù)器返回的響應(yīng),其獲取的cacheControl也是服務(wù)器響應(yīng)的頭部
這里不對(duì)內(nèi)部代碼進(jìn)行查看了 要不然文章太冗雜了
//判斷是否有首部Expires
if (response.header("Expires") != null
//判斷 Cache-Control是否含有max-age字段的值 如果沒(méi)有則為-1
|| response.cacheControl().maxAgeSeconds() != -1
//判斷 Cache-Control是否含有public 默認(rèn)為false
|| response.cacheControl().isPublic()
//判斷 Cache-Control是否含有private 默認(rèn)為false
|| response.cacheControl().isPrivate()) {
break;
}
所以如果響應(yīng)碼是302或者是307的時(shí)候 必須含有首部Expires 或者首部Cache-Control 有max-age public或者是private字段才可以繼續(xù)往下走
return !response.cacheControl().noStore() && !request.cacheControl().noStore();
這行代碼是指如果請(qǐng)求頭 或者響應(yīng)頭的Cache-Control 含有no-store 字段則肯定不緩存
走到這里我們看到 okhttp緩存的條件是
1,首先請(qǐng)求頭或者響應(yīng)頭的Cache-Control 不能含有no-store(一旦含有必定不緩存)
2,在滿足條件1的情況下
(1)如果響應(yīng)碼是是302或307時(shí) 必須含有首部Expires 或者首部Cache-Control 有max-age public或者是private字段
(2)響應(yīng)碼為200 203 300 301 308 404 405 410 414 501 時(shí)緩存
但是我們繼續(xù)看下面代碼(CacheInterceptor類中intercept方法)
//如果請(qǐng)求方法不是支持的緩存方法則刪除緩存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
那我們繼續(xù)看HttpMethod.invalidatesCache(networkRequest.method())方法,如下
public static boolean invalidatesCache(String method) {
return method.equals("POST")
|| method.equals("PATCH")
|| method.equals("PUT")
|| method.equals("DELETE")
|| method.equals("MOVE"); // WebDAV
}
也就是說(shuō)如果請(qǐng)求方法是 post put delete move patch 則刪除請(qǐng)求,我們應(yīng)該還能想起 前面在判斷是否有響應(yīng)體的時(shí)候有這么一行代碼
//Head方法 在服務(wù)器的響應(yīng)中只返回響應(yīng)頭,不會(huì)返回響應(yīng)體
if (response.request().method().equals("HEAD")) {
return false;
}
也就是說(shuō)緩存也是不支持head方法的
綜上 我們可以得到okhttp支持存儲(chǔ)的緩存 只能是get方法請(qǐng)求的響應(yīng),好了 我們記住這一條結(jié)論 后面我們還能得到這一印證(后面我們也會(huì)得到為什么緩存只支持get方法的原因)
在這里我們基本上已經(jīng)完成了okhhtp緩存條件的分析 不過(guò)還有一些小細(xì)節(jié)我們可能沒(méi)有分析到 我們繼續(xù)看 緩存存儲(chǔ)的方法put(也就是我們前面設(shè)置的緩存策略的put方法),這里的cache是個(gè)接口 但是它的真正實(shí)現(xiàn)是Cache類
所以我們直接看Cache類的 put方法就可以了
CacheRequest cacheRequest = cache.put(response);
Cache的put方法
@Nullable CacheRequest put(Response response) {
String requestMethod = response.request().method();
//這里再次對(duì)請(qǐng)求方法進(jìn)行檢驗(yàn)
if (HttpMethod.invalidatesCache(response.request().method())) {
try {
remove(response.request());
} catch (IOException ignored) {
// The cache cannot be written.
}
return null;
}
//再次驗(yàn)證如果不是get方法則返回,這里也解釋了 為什么只緩存get方法的請(qǐng)求而不緩存其他方法的請(qǐng)求響應(yīng),
//這里的解釋是 技術(shù)上可以做到 但是花費(fèi)的精力太大 而且效率太低 所以再次不做其他方法的緩存
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;
}
//此處是檢查 響應(yīng)頭部是否含有 * 如果含有* 也不緩存 這里我就不繼續(xù)分析了 大家可以自己看看
if (HttpHeaders.hasVaryAll(response)) {
return null;
}
Entry entry = new Entry(response);
DiskLruCache.Editor editor = null;
try {
editor = cache.edit(key(response.request().url()));
if (editor == null) {
return null;
}
entry.writeTo(editor);
return new CacheRequestImpl(editor);
} catch (IOException e) {
abortQuietly(editor);
return null;
}
}
好了 這里我們徹底的完成了 我們這一小節(jié)的主題 okhttp在什么情況下緩存網(wǎng)絡(luò)響應(yīng) 下面我們總結(jié)下我們得到的結(jié)論:
1、Okhhtp只緩存Get請(qǐng)求
2、如果請(qǐng)求頭或者響應(yīng)頭的Cache-Control 含有no-store 則一定不緩存
3、如果響應(yīng)頭含有*字符則不緩存
4、在滿足 1、2、3 的條件下 okhttp對(duì)以下的條件進(jìn)行緩存
(1)響應(yīng)碼是200 203 300 301 308 404 405 410 414 501 時(shí)緩存
(2)如果響應(yīng)碼是是302或307時(shí) 必須含有首部Expires 或者首部Cache-Control 有max-age public或者是private字段
好了上面我們分析完"okhttp在什么情況下緩存網(wǎng)絡(luò)響應(yīng)" 下面我們分析"okhhtp在什么情況下使用緩存而不是使用網(wǎng)絡(luò)請(qǐng)求"
okhhtp在什么情況下使用緩存而不是使用網(wǎng)絡(luò)請(qǐng)求
我們還是要研究緩存攔截器,研究的前提自然是我們的請(qǐng)求符合okhhtp存儲(chǔ)緩存的要求 而且已經(jīng)緩存成功了
好了我們先粘出緩存攔截器intercept方法全部?jī)?nèi)容
@Override public Response intercept(Chain chain) throws IOException {
//前面我們已經(jīng)知道了 這個(gè)cache就是我們?cè)O(shè)置的緩存策略
//這里是利用緩存策略根據(jù)請(qǐng)求獲取緩存的響應(yīng)(我們的前提是get請(qǐng)求并且已經(jīng)成功緩存了,所以我們這里成功的獲取了緩存響應(yīng))
//(1)
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
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.
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) {
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());
}
}
//下面代碼我們已經(jīng)分析過(guò)了
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (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;
}
我們先根據(jù)結(jié)果然后推論原因,這樣可能更好的理解
首先我們正確獲取了緩存的網(wǎng)絡(luò)響應(yīng)cacheCandidate 代碼位置為(1)
然后初始化得到一個(gè)CacheStrategy類 看名字緩存策略大概可以猜到這個(gè)是根據(jù)傳入的請(qǐng)求和緩存響應(yīng) 判斷到底是使用緩存還是進(jìn)行網(wǎng)絡(luò)請(qǐng)求(其實(shí)我們之前研究存儲(chǔ)緩存的時(shí)候就已經(jīng)看過(guò)了,這里我們?cè)敿?xì)研究下)
這里我們?cè)敿?xì)看下 看下這個(gè)類CacheStrategy內(nèi)部以及這個(gè)兩個(gè)返回值(注:這兩個(gè)返回值是否為空是緩存策略判斷的結(jié)果,為空則不支持 不為空則支持)
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
我們先看下 new CacheStrategy.Factory(now, chain.request(), cacheCandidate);也就是CacheStrategy的內(nèi)部類Factory的構(gòu)造方法
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
//這里將傳入的網(wǎng)絡(luò)請(qǐng)求和緩存響應(yīng)設(shè)置為Factory內(nèi)部成員
this.request = request;
this.cacheResponse = cacheResponse;
//cacheResponse不為空 所以執(zhí)行
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
//這里主要是解析緩存響應(yīng)頭部 并將解析到的字段賦值給Factory的成員變量 這里主要是獲取和緩存相關(guān)的字段
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = HttpDate.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = HttpDate.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = HttpDate.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
上面的代碼主要是解析緩存頭部字段 并存儲(chǔ)在Factory類的成員變量中,下面解釋下上面需要解析的和緩存相關(guān)的頭部字段(首部 不區(qū)分大小寫)
- Age 告訴接受端 響應(yīng)已經(jīng)產(chǎn)生了多長(zhǎng)時(shí)間(這個(gè)是和max-age一起實(shí)現(xiàn)緩存的),單位是秒
- Date 報(bào)文創(chuàng)建的時(shí)間和日期(響應(yīng)和報(bào)文產(chǎn)生的時(shí)間是不一樣的概念 時(shí)間也不一定相等)
- ETag 報(bào)文中包含的實(shí)體(響應(yīng)體)的標(biāo)志 實(shí)體標(biāo)識(shí)是標(biāo)志資源的一種方式,用老確定資源是否有修改
- Last-Modified 實(shí)體最后一次唄修改的時(shí)間 舉例說(shuō)明 : Last-Modified : Fri , 12 May 2006 18:53:33 GMT
我們會(huì)繼續(xù)看Factory的get方法
//獲取CacheStrategy實(shí)例
public CacheStrategy get() {
//獲取CacheStrategy實(shí)例
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;
}
我們繼續(xù)看getCandidate(),這個(gè)方法實(shí)現(xiàn)了基本上的緩存策略
---注:為了大家更好的理解下面的緩存策略,我先說(shuō)下okhttp緩存判斷依據(jù)結(jié)論 后面我們?cè)僮鲵?yàn)證
這里是CacheStrategy的構(gòu)造方法
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
重要這里說(shuō)下緩存判斷依據(jù)
1如果networkRequest為null cacheResponse不為null 則使用緩存
2如果networkRequest不為null cacheResponse為null 則進(jìn)行網(wǎng)絡(luò)請(qǐng)求
3其他情況我們下面再解釋 我們先記住這2個(gè)結(jié)論 這個(gè)能更好幫助我們理解下面的緩存策略判斷 大家可以先看這一小節(jié)的結(jié)論好 請(qǐng)求頭的注釋以及說(shuō)明 然后再看下面的緩存策略 這樣可能更好理解些(當(dāng)然如果對(duì)http的Cache
比較了解的話,則不必了)
private CacheStrategy getCandidate() {
// No cached response.
//如果緩存為空 則初始化一個(gè)CacheStrategy返回
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
//如果請(qǐng)求方式是https而且緩存的沒(méi)有TLS握手(注:這個(gè)我沒(méi)有深入的研究 如果大家有結(jié)論可以告訴我)
// 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.
//此時(shí)再次判讀是否應(yīng)該緩存 如果不應(yīng)該緩存則將cacheResponse設(shè)為null
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
//獲取請(qǐng)求的CacheControl字段的信息類
CacheControl requestCaching = request.cacheControl();
//如果請(qǐng)求含有nocache字段并且如果請(qǐng)求頭部含有If-Modified-Since或者If-None-Match字段(下面會(huì)介紹這些條件緩存字段的含義) 則將cacheResponse設(shè)為null
//hasConditions(request)的方法詳情如下:
//private static boolean hasConditions(Request request) {
// return request.header("If-Modified-Since") != null || request.header("If-None-Match") != null;
//}
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
//獲取緩存響應(yīng)的頭部Cache-Control字段的信息
CacheControl responseCaching = cacheResponse.cacheControl();
//Cache-Control字段是否含有immutable 前面我們已經(jīng)說(shuō)過(guò)immutable 是表明 響應(yīng)不會(huì)隨著時(shí)間而變化 這里將netrequest設(shè)置為null 并return(意思是使用緩存)
if (responseCaching.immutable()) {
return new CacheStrategy(null, cacheResponse);
}
//獲取緩存里面響應(yīng)的age 注意該age不是響應(yīng)的真實(shí)age
long ageMillis = cacheResponseAge();
//獲取緩存響應(yīng)里聲明的新鮮時(shí)間,如果沒(méi)有設(shè)置則為0
long freshMillis = computeFreshnessLifetime();
//如果請(qǐng)求中的含有最大響應(yīng)新鮮時(shí)間 則和緩存中的最大響應(yīng)新鮮時(shí)間進(jìn)行比較 取最小值并賦值給freshMillis
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
//獲取最小新鮮時(shí)間(前面我們提到過(guò)這個(gè)概念)
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
}
//獲取緩存過(guò)期后還能使用的時(shí)間
long maxStaleMillis = 0;
//判斷Cache-Control 是否含有must-revalidate字段 該字段我們前面說(shuō)到了是 使用緩存前必須對(duì)資源進(jìn)行驗(yàn)證 不可使用過(guò)期時(shí)間,換句話說(shuō)如果設(shè)置該字段了 那過(guò)期后還能使用時(shí)間就沒(méi)有意義了
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
//如果緩存頭部Cache-Control 不含有nocache,并且 (緩存響應(yīng)年齡+最小新鮮時(shí)間)<(響應(yīng)新鮮時(shí)間+響應(yīng)過(guò)期后還可用的時(shí)間) 則使用緩存,不過(guò)根據(jù)響應(yīng)時(shí)間還要加上一些警告 Warning
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
//如果(緩存響應(yīng)年齡+最小新鮮時(shí)間)<響應(yīng)新鮮時(shí)間 則加上警告頭部 響應(yīng)已經(jīng)過(guò)期
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
//獲取一天的時(shí)間(毫秒)
long oneDayMillis = 24 * 60 * 60 * 1000L;
//如果響應(yīng)年齡大于一天 并且響應(yīng)使用啟發(fā)式過(guò)期時(shí)間
//我們看下isFreshnessLifetimeHeuristic()就知道什么是啟發(fā)式過(guò)期了
//private boolean isFreshnessLifetimeHeuristic() {
//return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
//}
//好吧 啟發(fā)式過(guò)期 就是沒(méi)有設(shè)置獲取過(guò)期時(shí)間字段
//如果是這樣的 則需要添加警告頭部字段 這是試探性過(guò)期(不過(guò)如果使用緩存的話 不建議這么做)
//關(guān)于試探性過(guò)期 我們下面介紹 (稍安勿躁) 因?yàn)槠^多 這些寫不下
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.
//獲取請(qǐng)求的條件頭部 并根據(jù)條件來(lái)判斷是否使用緩存
String conditionName;
String conditionValue;
//判斷緩存響應(yīng)的資源 實(shí)體標(biāo)識(shí)是否為空
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
//緩存響應(yīng)頭部 lastModified 最后一次修改時(shí)間
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
//緩存響應(yīng)頭部 servedDate Date字段的值 就是 原始服務(wù)器發(fā)出響應(yīng)時(shí)的服務(wù)器時(shí)間
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
//如果上述字段都沒(méi)有則發(fā)出原始的網(wǎng)絡(luò)請(qǐng)求 不使用緩存
return new CacheStrategy(request, null); // No condition! Make a regular request.
}
//如果上述的條件之一滿足 則添加條件頭部 返回含有條件頭部的請(qǐng)求和緩存響應(yīng)
//復(fù)制一份和當(dāng)前request的header
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
//在header中添加條件頭部, 注:Internal是個(gè)抽象方法 該方法的唯一實(shí)現(xiàn)是在okhhtpclient的內(nèi)部實(shí)現(xiàn) okhhtpclient本身沒(méi)有實(shí)現(xiàn)它
Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
//刪除原有的所有header 將現(xiàn)在的header添加上 注意上面已經(jīng)說(shuō)過(guò)了 添加的header 是復(fù)制原來(lái)的header 并在它的基礎(chǔ)上添加一個(gè)條件頭部
.headers(conditionalRequestHeaders.build())
.build();
//返回處理過(guò)的 請(qǐng)求和 緩存響應(yīng)
return new CacheStrategy(conditionalRequest, cacheResponse);
}
okhhtp的緩存策略分析完了 但是大家可能對(duì)上面出現(xiàn)的頭部字段和條件字段 以及新鮮度什么的比較迷糊,這里我們先說(shuō)下這些東西 也好大家更好的理解
好吧我們先說(shuō)下新鮮度( 這里說(shuō)明:以下所說(shuō)的服務(wù)器都是源服務(wù)器就是最初發(fā)出響應(yīng)報(bào)文的服務(wù)器,代理服務(wù)器會(huì)特別說(shuō)明是代理服務(wù)器)
http為了實(shí)現(xiàn)緩存 提出了新鮮度的概念 那什么是新鮮度呢?比如一個(gè)西紅柿 剛從地里摘下拿回家放著,那它就是新鮮的,而且是最新鮮,那過(guò)了3天后 這個(gè)西紅柿壞掉了 那它就是不新鮮的了 不能用了,
從地里摘掉到壞掉的這3天時(shí)間內(nèi) 就是他的新鮮時(shí)間,這個(gè)在http中 土地相當(dāng)于服務(wù)器,家里存放相當(dāng)于緩存,客戶端使用就相當(dāng)于要吃掉這個(gè)西紅柿
好了新鮮度的大概意思我們了解了 我們?cè)谠敿?xì)的說(shuō)說(shuō) http針對(duì)新鮮度設(shè)置的各個(gè)頭部
age :年齡 從響應(yīng)產(chǎn)生的時(shí)間(單獨(dú)字段 不屬于Cache-Control)
max-age:最大年齡 響應(yīng)可以產(chǎn)生的最大年齡 只看該字段的話 我們可以說(shuō) 該響應(yīng)的新鮮時(shí)間是max-age 也就是說(shuō)只要 age<max-age 該響應(yīng)就是新鮮的 是可以用的不用請(qǐng)求服務(wù)器客戶已直接使用緩存(屬于 Cache-Control的命令)
上面2個(gè)字段在計(jì)算新鮮度時(shí)具有最高優(yōu)先級(jí)
expires :過(guò)期時(shí)間 這個(gè)字段是說(shuō)明響應(yīng)過(guò)期的時(shí)間點(diǎn)(單獨(dú)字段 不屬于Cache-Control)
這個(gè)字段的優(yōu)先級(jí)其次
date :日期 這個(gè)是指服務(wù)器產(chǎn)生響應(yīng)的時(shí)間
注:如果上面3個(gè)字段都沒(méi)有設(shè)置 可以用這個(gè)字段計(jì)算新鮮時(shí)間 這個(gè)時(shí)間也叫做試探性過(guò)期時(shí)間 計(jì)算方式 采用LM_Factor算法計(jì)算
方式如下 time_since_modify = max(0,date-last-modified);
freshness_time = (int)(time_since_modify*lm_factor);在okhttp中這個(gè)lm_factor值是10%,就是0.1;
詳細(xì)解釋下:time_since_modify = max(0,date - last-modified) 就是將服務(wù)器響應(yīng)時(shí)間(date) 減去 服務(wù)器最后一次修改資源的時(shí)間(last-modified) 得到一個(gè)時(shí)間差 用這個(gè)時(shí)間差和0比較取最大值得到time_since_modify,
freshness_time = (int)(time_since_modify*lm_factor); 前面我們已經(jīng)得到time_since_modify 這里我們?nèi)∑渲幸恍《螘r(shí)間作為過(guò)期時(shí)間,lm_factor就是這一小段的比例,在okhttp中比例是10%
注:date時(shí)間比修改使勁越大說(shuō)明資源修改越不頻繁(新鮮度越大) ,
在前面分析緩存策略的時(shí)候有這么一行代碼 我當(dāng)時(shí)的解釋是獲取新鮮度 但是并沒(méi)有詳細(xì)的分析這個(gè)方法,這里我們進(jìn)去看看 這個(gè)計(jì)算新鮮度的算法是否和我們上面分析的一致
long freshMillis = computeFreshnessLifetime();
computeFreshnessLifetime()方法如下:
private long computeFreshnessLifetime() {
CacheControl responseCaching = cacheResponse.cacheControl();
//這個(gè)是我們前面說(shuō)的 max-age是優(yōu)先級(jí)最高的計(jì)算新鮮時(shí)間的方式 和我們說(shuō)的一致
if (responseCaching.maxAgeSeconds() != -1) {
return SECONDS.toMillis(responseCaching.maxAgeSeconds());
//當(dāng)沒(méi)有max-age字段時(shí) 使用expires - date 獲取新鮮度時(shí)間 和我們說(shuō)的也一致
} else if (expires != null) {
long servedMillis = servedDate != null
? servedDate.getTime()
: receivedResponseMillis;
long delta = expires.getTime() - servedMillis;
return delta > 0 ? delta : 0;
//當(dāng)上述2個(gè)字段都不存在時(shí) 進(jìn)行試探性過(guò)期計(jì)算 還是和我們說(shuō)的一致
} else if (lastModified != null
&& cacheResponse.request().url().query() == null) {
// As recommended by the HTTP RFC and implemented in Firefox, the
// max age of a document should be defaulted to 10% of the
// document's age at the time it was served. Default expiration
// dates aren't used for URIs containing a query.
long servedMillis = servedDate != null
? servedDate.getTime()
: sentRequestMillis;
long delta = servedMillis - lastModified.getTime();
return delta > 0 ? (delta / 10) : 0;
}
//當(dāng)上述方式3種方式都不可取時(shí)返回新鮮時(shí)間0 也就是說(shuō)不能獲取緩存 一定要進(jìn)行網(wǎng)絡(luò)請(qǐng)求
return 0;
}
好了經(jīng)過(guò)上面的討論 我們知道了新鮮度 以及如何計(jì)算一個(gè)響應(yīng)的新鮮時(shí)間,接下來(lái)緩存真正的可用的時(shí)間 上面我們看到了Cache-control的這兩個(gè)字段
min-fresh:最小要保留的新鮮度 ,即是假如緩存設(shè)置的最大新鮮時(shí)間為max-age=500 最小新鮮度為min-fresh=300 則該緩存的真正的新鮮時(shí)間 是max-age-min-fresh=200 也就是說(shuō)緩存在200秒內(nèi)有效 超過(guò)200秒就必須要請(qǐng)求服務(wù)器驗(yàn)證
max-stale:緩存過(guò)期后還可以用的時(shí)間
也就是緩存真正的可用的新鮮時(shí)間是 realFreshTime =(freshTime+max-stale) - (min-fresh);
現(xiàn)在我們應(yīng)該對(duì)上面分析的緩存策略更明白點(diǎn)了,下面我們分析上面我們說(shuō)過(guò)的 緩存的命令頭部
- If-Modify-since:date , 條件判斷 如果在date時(shí)間之后服務(wù)器沒(méi)有修改該資源 就返回304 并且在頭部添加新的資源過(guò)期時(shí)間,如果修改了則返回200并且正確返回正確的請(qǐng)求資源以及必要響應(yīng)頭部,注意這個(gè)date我們一般設(shè)置為服務(wù)器上次修改資源的時(shí)間即是:Last-Modified,因?yàn)橛行┓?wù)器在處理這個(gè)條件緩存判斷是是將該時(shí)間當(dāng)做字符串和上次修改的時(shí)間進(jìn)行比對(duì),如果相同則返回304否則返回200,不過(guò)這也違反了該條件字段設(shè)計(jì)的初衷,
- If-None-Match:ETag ,條件判斷 ,我們前面解釋了什么是ETag,即實(shí)體標(biāo)識(shí),如果客戶端發(fā)出的請(qǐng)求帶有這個(gè)條件判斷,服務(wù)器會(huì)根據(jù)服務(wù)器當(dāng)前的ETag和客戶端給出的ETag進(jìn)行比對(duì)如果相同,則返回304即是服務(wù)器資源沒(méi)變,繼續(xù)使用緩存,如果不相同,即是服務(wù)器資源發(fā)生改變,這是服務(wù)器返回200,以及新的ETag實(shí)體標(biāo)識(shí),還有請(qǐng)求的資源(就是當(dāng)做正常請(qǐng)求一樣),
我們現(xiàn)在將okhttp涉及到的http知識(shí)都說(shuō)完了 那么我們繼續(xù)上面的分析
上面我們分析完 private CacheStrategy getCandidate() {} 我之前說(shuō)過(guò)這個(gè)是okhttp緩存策略的真正體現(xiàn) 那么我們對(duì)getCandidate()方法從頭到尾的總結(jié)一遍
- 首先根據(jù)緩存的響應(yīng)碼判斷是否可以緩存 不可以返回 cacheResponse為null
- 上述條件都不滿足 判斷請(qǐng)求頭部是否含有no-cache以及頭部是否含有條件頭部If-Modify-since或者If-None-Match,三個(gè)條件滿足一個(gè) cacheResponse為null 返回
- 上述條件都不滿足 判斷cache-control 是否有immutable 有的話 使用緩存 networkRequest為null 返回
- 上述條件都不滿足 計(jì)算age 最小新鮮時(shí)間minFresh 新鮮時(shí)間Fresh 過(guò)期可使用時(shí)間stale 如果age+minfresh<fresh+stale 說(shuō)明當(dāng)前響應(yīng)滿足新鮮度 networkRequest為null 返回
- 判斷緩存存儲(chǔ)的響應(yīng)是否含有響應(yīng)頭ETag,date,Last-Modified其中的一個(gè)或多個(gè)字段如果沒(méi)有則返回 cacheResponse為null 返回 ,如果有,則先判斷ETag是否存在如果存在 則在networkRequest請(qǐng)求頭部添加If-None-Match:ETag,返回(cacheResponse,networkRequest)都不為空,如果ETag不存在 則date,Last-Modified(Last-Modified優(yōu)先級(jí)大于date)是否存在,存在則在networkRequest請(qǐng)求頭部添加If-Modify-since:date并返回
上面就是getCandidate()的全部流程,我們繼續(xù)分析public CacheStrategy get() {}方法
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
//就是如果緩存策略要進(jìn)行網(wǎng)絡(luò)請(qǐng)求,但是請(qǐng)求中又設(shè)置要使用緩存則將cacheResponse,networkRequest全部為null,好了這個(gè)判斷我們可以在上面分析getCandidate()方法中加上一條
//6 ,就是如果緩存策略要進(jìn)行網(wǎng)絡(luò)請(qǐng)求,但是請(qǐng)求中又設(shè)置要使用緩存onlyIfCache則將cacheResponse,networkRequest全部為nul
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的策略分析 那我們繼續(xù)回到okhttp的網(wǎng)絡(luò)緩存攔截器CacheInterceptor分析我們沒(méi)有分析完的代碼
@Override public Response intercept(Chain chain) throws IOException {
....
//我們上面已經(jīng)分析完CacheStrategy的初始化方法 也知道了networkRequest,cacheResponse為空的條件和含義
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
//這個(gè)是設(shè)置追蹤和我們要分析的無(wú)關(guān)
if (cache != null) {
cache.trackResponse(strategy);
}
//如果我們?cè)O(shè)置了緩存策略 但是卻使用緩存則關(guān)閉緩存
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.
//此處就是我們上面總結(jié)的第六條,如果我們想要進(jìn)行網(wǎng)絡(luò)請(qǐng)求但是卻在cache-control設(shè)置了 only-if-cache(如果有緩存就使用緩存),則返回一個(gè)空的響應(yīng)體并且響應(yīng)碼是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();
}
//如果networkRequest為null 我們就將緩存的響應(yīng)返回,也就是我們使用了緩存,到此我們的分析算是大部分結(jié)束了,
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
//進(jìn)行網(wǎng)絡(luò)請(qǐng)求獲取網(wǎng)絡(luò)響應(yīng) 但是需要注意的是這個(gè)networkRequest是經(jīng)過(guò)okhhtp改變過(guò)的 就是可能加上了條件緩存頭字段
//所以這個(gè)請(qǐng)求可能是304 就是服務(wù)器資源沒(méi)有改變,沒(méi)有返回響應(yīng)體
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.
//如果cacheResponse不為null
if (cacheResponse != null) {
//如果網(wǎng)絡(luò)響應(yīng)碼是304的話 則返回緩存響應(yīng)并更新緩存(如果緩存頭部和請(qǐng)求得到的頭部不一致則使用請(qǐng)求得到的頭部)
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()).
cache.trackConditionalCacheHit();
//更新緩存
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
......
}
到此我們這一小節(jié)的分析徹底的結(jié)束了,下面我們總結(jié)下整個(gè)流程
++++++++++++++++++++++這個(gè)很重要+++++++++++++++++++++++++++++++++
我們總結(jié)下okhhtp在什么請(qǐng)求下使用緩存
- 首先我們要設(shè)置緩存策略Cache,(否則肯定不會(huì)緩存響應(yīng),當(dāng)然也不會(huì)使用緩存響應(yīng),也不會(huì)設(shè)置緩存條件頭部, 關(guān)于這個(gè)緩存策略我們也可以定義自己實(shí)現(xiàn)InternalCache接口 然后在okhttpclient.build中設(shè)置,注意自定義的緩存策略和Cache只能有一個(gè),) 如果沒(méi)有設(shè)置返回 ->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
- 判斷緩存中是否含有該網(wǎng)絡(luò)請(qǐng)求的緩存如果沒(méi)有返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
- 判斷請(qǐng)求方法是否支持,如果不支持返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
- 判斷請(qǐng)求頭cache-control是否含有nocache,如果有返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
- 判斷請(qǐng)求頭是否含有條件判斷字段If-Modified-Since或者是If-None-Match,如果有返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
- 判斷請(qǐng)求頭cache-control是否含有immutable字段 如果有則返回緩存
- 判斷緩存新鮮度是否滿足 滿足返回緩存
- 判斷請(qǐng)求頭部是否含有Date,ETag,Last-Modified 其中一個(gè)或者多個(gè)字段 沒(méi)有則返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求
- 根據(jù)ETag>Last-Modified>Date的優(yōu)先級(jí)生成 If-None-Match:ETag>If-Modified-Since:Last-Modified>If-Modified-Since:Date的條件驗(yàn)證字段,但是只能生成一個(gè) 返回 >如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
- 含有條件首部的網(wǎng)絡(luò)請(qǐng)求返回304的時(shí)候返回緩存,并更新緩存
注:
1.上述判斷的執(zhí)行順序是從1到10只有上面的條件滿足才可以執(zhí)行下面的邏輯
2.上述的3中請(qǐng)求方法只支持get和head方法
3.詳細(xì)上述的7中的情況我在上面已經(jīng)解釋了 不明白可以往上翻翻
okhttp整個(gè)緩存流程圖如下:
++++++++++++++++++++++這個(gè)很重要+++++++++++++++++++++++++++++++++