okhhtp緩存篇_上

okhhtp緩存篇_上#

注:本文分析的基礎(chǔ)是在大概了解了okhhtp的基礎(chǔ)上分析的如果不了解的話建議看下okhhtp的網(wǎng)絡(luò)流程https://blog.csdn.net/wkk_ly/article/details/81004920

前言:okhttp_緩存篇分為上下兩部分,主要從以下幾個(gè)方面來(lái)分析okhhtp緩存

  1. okhttp緩存如何聲明使用緩存
  2. okhttp在什么情況下緩存網(wǎng)絡(luò)響應(yīng)(這個(gè)涉及我們更好的使用okhttp緩存)
  3. okhhtp在什么情況下使用緩存而不是使用網(wǎng)絡(luò)請(qǐng)求(這個(gè)涉及我們更好的使用okhttp緩存)
  4. okhhtp如何存儲(chǔ)緩存的
  5. okhhtp是如何取出緩存的
  6. 總結(jié)okhhtp的緩存&&使用
  7. 附錄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)碼

  1. 1XX: 100-199 的狀態(tài)碼代表著信息性狀態(tài)碼
  2. 2xx: 200-299 的狀態(tài)碼代表著成功狀態(tài)碼,但是204狀態(tài)碼代表著沒(méi)有實(shí)體僅僅有響應(yīng)行和響應(yīng)頭
  3. 3xx: 300-399 的狀態(tài)碼代表著重定向 但是304狀態(tài)碼代表著請(qǐng)求資源未修改,請(qǐng)使用緩存,只有響應(yīng)行和響應(yīng)頭沒(méi)有響應(yīng)體
  4. 4xx: 400-499 的狀態(tài)碼代表著客戶端錯(cuò)誤代碼
  5. 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è)字段


TIM截圖20180712154651.png

Expires 指的是請(qǐng)求資源的過(guò)期時(shí)間源于http1.0
Cache-Control :這個(gè)是目前在做緩存時(shí)最重要的一個(gè)字段,用來(lái)指定緩存規(guī)則,他有很多的值,不同的值代表著不同的意義,而且這個(gè)值是可以組合的 如下圖


TIM截圖20180712155245.png

說(shuō)下 Cache-Control各個(gè)字段的不同含義(首部字段不區(qū)分大小寫):

  1. no-cache :在使用該請(qǐng)求緩存時(shí)必須要先到服務(wù)器驗(yàn)證 寫法舉例 Cache-Control:no-cache
  2. no-store :不緩存該請(qǐng)求,如果緩存了必須刪除緩存 寫法舉例 Cache-Control:no-store
  3. max-age :該資源有效期的最大時(shí)間 寫法舉例 Cache-Control:max-age=3600
  4. s-maxage :和max-age 類似 不過(guò)該字段是用于共享(公共)緩存 寫法舉例 Cache-Control:s-maxage=3600
  5. private :表明響應(yīng)只可以被單個(gè)用戶緩存 不能被代理緩存 寫法舉例 Cache-Control:private
  6. public :表明響應(yīng)可以被任何用戶緩存 是共享(例如 客戶端 代理服務(wù)器等等) 寫法舉例 Cache-Control:public
  7. must-revalidate:緩存必須在使用之前驗(yàn)證舊資源的狀態(tài),并且不可使用過(guò)期資源。 寫法舉例 Cache-Control:must-revalidate
  8. 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ù)用)
  9. 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)證
  10. only-if-cached:如果緩存存在就使用緩存 無(wú)論服務(wù)器是否更新(無(wú)論緩存是否過(guò)期) 寫法舉例 Cache-Control:only-if-cached
  11. 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
  12. 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ū)分大小寫)

  1. Age 告訴接受端 響應(yīng)已經(jīng)產(chǎn)生了多長(zhǎng)時(shí)間(這個(gè)是和max-age一起實(shí)現(xiàn)緩存的),單位是秒
  2. Date 報(bào)文創(chuàng)建的時(shí)間和日期(響應(yīng)和報(bào)文產(chǎn)生的時(shí)間是不一樣的概念 時(shí)間也不一定相等)
  3. ETag 報(bào)文中包含的實(shí)體(響應(yīng)體)的標(biāo)志 實(shí)體標(biāo)識(shí)是標(biāo)志資源的一種方式,用老確定資源是否有修改
  4. 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ò)的 緩存的命令頭部

  1. 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ì)的初衷,
  2. 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é)一遍

  1. 首先根據(jù)緩存的響應(yīng)碼判斷是否可以緩存 不可以返回 cacheResponse為null
  2. 上述條件都不滿足 判斷請(qǐng)求頭部是否含有no-cache以及頭部是否含有條件頭部If-Modify-since或者If-None-Match,三個(gè)條件滿足一個(gè) cacheResponse為null 返回
  3. 上述條件都不滿足 判斷cache-control 是否有immutable 有的話 使用緩存 networkRequest為null 返回
  4. 上述條件都不滿足 計(jì)算age 最小新鮮時(shí)間minFresh 新鮮時(shí)間Fresh 過(guò)期可使用時(shí)間stale 如果age+minfresh<fresh+stale 說(shuō)明當(dāng)前響應(yīng)滿足新鮮度 networkRequest為null 返回
  5. 判斷緩存存儲(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)求下使用緩存

  1. 首先我們要設(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)求,
  2. 判斷緩存中是否含有該網(wǎng)絡(luò)請(qǐng)求的緩存如果沒(méi)有返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
  3. 判斷請(qǐng)求方法是否支持,如果不支持返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
  4. 判斷請(qǐng)求頭cache-control是否含有nocache,如果有返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
  5. 判斷請(qǐng)求頭是否含有條件判斷字段If-Modified-Since或者是If-None-Match,如果有返回->如果cachecontrol 設(shè)置了only-if-cached則返回504 沒(méi)有設(shè)置則進(jìn)行網(wǎng)絡(luò)請(qǐng)求,
  6. 判斷請(qǐng)求頭cache-control是否含有immutable字段 如果有則返回緩存
  7. 判斷緩存新鮮度是否滿足 滿足返回緩存
  8. 判斷請(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)求
  9. 根據(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)求,
  10. 含有條件首部的網(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è)緩存流程圖如下:

okhttp緩存流程圖.jpg

++++++++++++++++++++++這個(gè)很重要+++++++++++++++++++++++++++++++++


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。