源碼分析三:OkHttp—CacheInterceptor

本文分析okHttp緩存攔截器,能夠首先了解http緩存原理: 完全弄懂HTTP緩存機制及原理

緩存攔截器基本流程

  1. 讀取候選緩存;
  2. 建立緩存策略(根據頭信息,判斷強制緩存,對比緩存等策略);
  3. 根據策略,不使用網絡,緩存又沒有直接報錯;
  4. 根據策略,不使用網絡,有緩存就直接返回;
  5. 前面個都沒有返回,讀取網絡結果(跑下一個攔截器);
  6. 接收到的網絡結果,若是是code 304, 使用緩存,返回緩存結果(對比緩存)
  7. 讀取網絡結果;
  8. 對數據進行緩存;
  9. 刪除無效緩存;
  10. 返回網絡讀取的結果。

源碼解讀

@Override public Response intercept(Chain chain) throws IOException {
   //1. 讀取候選緩存;
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();

    //2. 建立緩存策略(強制緩存,對比緩存等策略);
    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();
    }

    // 4. 根據策略,不使用網絡,有緩存就直接返回;
    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
     // 5. 前面個都沒有返回,讀取網絡結果(跑下一個攔截器);
      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());
      }
    }

   //6. 接收到的網絡結果,若是是code 304, 使用緩存,返回緩存結果(對比緩存)
    // 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());
      }
    }

   //7. 讀取網絡結果;
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //8. 對數據進行緩存;
    if (HttpHeaders.hasBody(response)) {
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }
    //9. 返回網絡讀取的結果。
    return response;
  }複製代碼

拆開來看:html

一、讀取候選緩存

Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
複製代碼

二、建立緩存策略(根據頭信息,判斷強制緩存,對比緩存等策略)

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;複製代碼

構建請求策略,進入這個方法。
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);
      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);
      }
    }
  }
}複製代碼
「Date」,「Expires」,「Last-Modified」,「ETag」,「Age」這幾個單詞有沒有很熟?以前推薦的文章裏有講過。全都都是header裏的標誌,這裏就是用來獲取header裏標誌的內容。
/** The request to send on the network, or null if this call doesn't use the network. */ public final @Nullable Request networkRequest; /** The cached response to return or validate; or null if this call doesn't use a cache. */
public final @Nullable Response cacheResponse;複製代碼

根據CacheStrategy裏的註釋,能夠知道java

  • networkRequest:無網絡即爲null,有網絡則爲將要發送的請求在的網絡部分。
  • cacheResponse:無緩存即爲null,有緩存則爲緩存的響應,用來返回或驗證。


三、根據策略,無網絡、沒有緩存,直接報錯

無網絡無cache直接報錯,「Unsatisfiable Request (only-if-cached)」錯誤代碼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();
    }複製代碼

四、根據策略,無網絡、有緩存,就直接返回

若是你寫自定義interceptor的時候遇到這個error,不必定是你代碼的問題,極可能是服務端沒有配合。
bash

// If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }複製代碼

五、前面個都沒有返回,讀取網絡結果(跑下一個攔截器)

Response networkResponse = null;
    try {
     // 5. 前面個都沒有返回,讀取網絡結果(跑下一個攔截器);
      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());
      }
    }複製代碼

六、接收到的網絡結果,若是是code 304, 使用緩存,返回緩存結果(對比緩存)

// 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()); } } 複製代碼

七、讀取網絡結果;

Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();複製代碼

八、對數據進行緩存;

if (HttpHeaders.hasBody(response)) {
      CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
      response = cacheWritingResponse(cacheRequest, response);
    }複製代碼

其中cacheWritingResponse()經過Okio以及DiskLruCache來實現。網絡

九、刪除無效緩存

if (HttpMethod.invalidatesCache(networkRequest.method())) {
  try {
    cache.remove(networkRequest);
  } catch (IOException ignored) {
    // The cache cannot be written.
  }
}複製代碼

十、返回網絡讀取的結果。

return response;複製代碼
相關文章
相關標籤/搜索