1. 程式人生 > >以 Okhttp3原始碼 為例 ------ 圖解 快取機制 的原理和實現(下)

以 Okhttp3原始碼 為例 ------ 圖解 快取機制 的原理和實現(下)

之前寫的一篇是介紹快取機制的流程和原理,並講解了HTTP協議中快取相關的欄位,徹底瞭解了快取機制原理後實踐了Okhttp3框架的快取實現,即第一篇的內容已經打下了基礎,此篇就從原始碼的角度來解析Okhttp3框架的快取機制的實現。

在分析原始碼之前,提醒大家注意一點,我在之前的博文中也有分析過原始碼。剛開始去分析某個框架原始碼時,一定不要過於詳細糾結每一個方法中具體實現、操作等等,先把重點放在整體流程的梳理,理解透徹之後當你真需要去了解每個方法細節時,再去詳細分析。

一. OKhttp3 快取機制整體流程

在上篇部落格中已列出一個例子來實踐Okhttp3框架的快取機制,這是一個舉出的例子,使用Okhttp3進行網路請求,並設定快取屬性,程式碼如下:

public class CacheHttp {
    public static void main(String args[]) throws IOException {
        int maxCacheSize = 10 * 1024 * 1024;

        Cache cache = new Cache(new File("/Users/nate/source"), maxCacheSize);
        OkHttpClient client = new OkHttpClient.Builder().cache(cache).build();

        Request request = new Request.Builder
().url("http://www.qq.com/"). cacheControl(new CacheControl.Builder().maxStale(365, TimeUnit.DAYS).build()). build(); Response response = client.newCall(request).execute(); String body1 = response.body().string(); System.out.println("network response "
+ response.networkResponse()); System.out.println("cache response " + response.cacheResponse()); System.out.println("**************************"); Response response1 = client.newCall(request).execute(); String body2 = response1.body().string(); System.out.println("network response " + response1.networkResponse()); System.out.println("cache response " + response1.cacheResponse()); } }

以上程式碼已經使用了OKhttp3框架中的快取機制,接下來從原始碼角度瞭解其實現。首先檢視請求最終呼叫方法 —— client.newCall(request).execute() ,其實execute()Call介面中的方法,檢視其實現類:RealCall

1. 入口 —— RealCall 類

(1)execute() 方法

【RealCall 類】

@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

首先看try方法塊,通過getResponseWithInterceptorChain() 獲取一個Response響應物件,此方法應該是處理一些請求相關的資訊,來具體檢視實現:

(2)getResponseWithInterceptorChain()方法

  private Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //☆☆☆☆☆快取攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!retryAndFollowUpInterceptor.isForWebSocket()) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(
        retryAndFollowUpInterceptor.isForWebSocket()));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }
}

從此方法可以看出Okhttp3 採用了一種攔截機制,在新增攔截器時發現一個類為 CacheInterceptor ,顧名思義,猜測與快取相關的處理在此攔截器中進行。注意在建立此類時需要往建構函式中傳參 —— client.interceptors() ,實際上它是Okhttp3 內部的一個快取Cache,參看快取攔截器CacheInterceptor其具體實現:

2. 核心 —— 快取攔截器CacheInterceptor類的 intercept方法

此類主要快取邏輯處理是在intercept 方法中進行,以下將方法分成兩部分來進行講解。

(1)intercept 方法上半部分

【CacheInterceptor 類】

@Override public Response intercept(Chain chain) throws IOException {
    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(EMPTY_BODY)
          .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響應變數,首先來了解此方法中至關重要的區域性變數:

  • Request networkRequest
    網路請求,若此請求不需要使用到網路(即直接從快取獲取),則為null。
  • Response cacheResponse
    快取響應,若此次請求不使用快取(即快取失效的情況下需請求網路資源),則為null。

瞭解以上兩個變數後,得知它是在建立CacheStrategy類後獲取的其成員變數。需注意此類是快取機制的重點,代表為Okhttp3框架中的快取攔截策略,直接決定此方法的返回值Response響應。它的主要邏輯就是判斷當前本地快取是否有效,若快取依然有效,則填充cacheResponse快取響應物件;若無效,則填充networkRequest網路請求物件,而intercept 方法會根據這兩個值做下一步的判斷。(CacheStrategy類原始碼具體實現後續會介紹,這裡先將流程走通,讀者此時有個大致認識即可。)

根據判斷這兩個物件是否為null值,來決定返回結果,流程圖如下:

這裡寫圖片描述

可以發現,CacheInterceptor類的 intercept方法主要判斷流程是根據 網路請求物件networkRequest 和 快取響應物件cacheResponse決定的,它們存在的可能情況有以下三種:

  • 網路請求物件、快取響應物件皆為null
  • 網路請求物件為null,快取響應物件不為null
  • 網路請求物件不為null,(快取物件為null或不為null)

    intercept方法的上半部分已經判斷了以上兩種情況,接下來最後一種情況在下半部分邏輯。注意最後一種情況,當網路請求物件不為null時,表明CacheStrategy快取策略類判斷後需要進行網路請求,也意味著本地快取物件已失效,所以此時的快取物件為null與否並非決定性關鍵了。

再來具體檢視下半部分邏輯:

(2)intercept 方法下半部分

【CacheInterceptor 類】

@Override public Response intercept(Chain chain) throws IOException {
    ......
    //接上一節
    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 (validate(cacheResponse, networkResponse)) {
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .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);
    }

    return response;
   }

這下半部分的前提判斷條件是:網路請求物件不為null,快取物件為null或不為null。首先了解此方法中至關重要的區域性變數:

  • Response networkResponse
    請求網路後的響應結果物件
  • Response response
    最終返回物件

它的主要邏輯是執行新的網路請求,最終返回response響應物件,至於第一個networkResponse響應物件則是用於執行網路請求獲取資源資料。檢視其原始碼,以流程圖來解釋邏輯:

這裡寫圖片描述

這就是快取攔截器CacheInterceptor類的 intercept方法的主要邏輯,此方法是Okhttp3框架處理快取的關鍵!

3. CacheInterceptor類的 intercept方法的總流程圖:

這裡寫圖片描述

二. 細節解析 —— CacheStrategy 快取策略類

以上講解了處理快取的整體流程,在瞭解大體流程後,接下來詳細解析流程中的幾個重要部分,正好迎合了一開頭提給大家的建議:分析原始碼時,先了解整體過程,再從細節入手分析,接下來來講解快取策略器是如何讓做相應的判斷:

1. 原理圖解

這裡寫圖片描述

此圖在上篇文章中已經講解過,(詳細講解可砍上篇文章)CacheStrategy 快取策略類中的邏輯原理就是上圖流程所示,這裡簡單敘述流程

當客戶端請求資源時,首先判斷本地是否有快取,在有快取的情況下判斷它是否過期,若已經過期再判斷Etag標識值是否存在(此值不是必須存在):

  • 如果存在便會向伺服器傳送請求,在請求時會攜帶引數,引數If-None-Match會將Etag標記上一起傳送給伺服器。伺服器再決策Etag是否過期,根據返回的響應碼來決定從快取讀取還是請求響應。

  • 如果不存在,會再次判斷 Last-Modified欄位是否存在,如果存在的話,如同Etag一樣會向伺服器傳送請求,只是攜帶引數是會用If-Modified-Since去標識Last-Modified欄位,一起傳送給服務。在伺服器決策時,會將Last-Modified與伺服器修改檔案的時間進行比較,若無變化則直接從快取中讀取,否則請求響應,接收新的資料。

2. CacheStrategy 快取策略類的建立 —– Factory()

CacheStrategy 快取策略類 遵循以上圖解流程的這樣一套規則,接下來從真正原始碼的角度來解析其實現。此類的呼叫是在CacheInterceptor類的 intercept方法裡:

【CacheInterceptor 類】
@Override public Response intercept(Chain chain) throws IOException {
    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;
    ......
    }

CacheStrategy 策略快取類的狗仔是在其靜態方法Factory 中,這裡很明顯的運用了工廠模式,傳遞了三個引數:

- now :是當前請求資源時間,將用於校驗快取是否過期。
- chain.request() : 一個Request物件,當前請求頭的引數。
- cacheCandidate : 一個Response物件,如果當前的快取目錄(這裡的快取目錄是cache,型別為InternalCache)不等於null,則從快取目錄中取出對應請求的Response資料。

繼續深入,瞭解其CacheStrategy 類具體建立實現:

【CacheStrategy 類 】

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);
          }
        }
      }
    }

這裡首要的就是判斷從快取目錄中讀取的資料cacheResponse 是否為空,只有在不為空的情況下才可以取出響應頭中的資料,在if語句塊中採用迴圈查詢對應的幾個識別符號進行賦值。這幾個識別符號分別為”Date”、”Expires”、Last-Modified”、”ETag”、”Age”,注意並非每個識別符號都存在於快取資料中,例如上篇部落格中的測試快取騰訊網,只包含”Date”、”Expires”識別符號。

迴圈中的賦值主要是為了填充CacheStrategy 類的兩個重要區域性變數:cacheResponse快取響應物件和networkRequest網路請求物件。它們決定著Okhttp3框架快取機制的核心方法 —— CacheInterceptor類的 intercept方法的核心邏輯。

3. CacheStrategy 類中區域性變數的獲取 —— get()

【CacheInterceptor 類】

CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();

CacheStrategy 類的建立方法分析完後還沒有結束,以上只是填充了兩個區域性變數,但是別忘了在CacheInterceptor類的 intercept方法中獲取CacheStrategy 類最終呼叫是 get 方法,來檢視其具體實現:

【CacheStrategy 類】

    public CacheStrategy get() {
    //getCandidate()才是真正邏輯判斷實現 ☆☆☆☆☆
      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;
    }

主要的就是呼叫getCandidate() 方法獲取快取策略類並返回,所以此方法才是真正處理快取是否失效的核心邏輯方法,檢視其實現(小河裡也將該方法分成上下兩部分來分別解析):

(1)getCandidate() 的上半部分程式碼解析

【CacheStrategy 類】

 private CacheStrategy getCandidate() {
      // No cached response.
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }

      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
//----------上部分為返回快取響應為null的情況-------------

//----------下部分為判斷快取時間是否失效邏輯-------------
      ......
    }

getCandidate() 方法的上半部分主要是返回無快取響應Response物件的快取策略類CacheStrategy ,即必定請求網路資源的情況,這裡通過四種不同情況去判斷,以下流程圖分析其過程:

這裡寫圖片描述

(2)getCandidate() 的下半部分程式碼解析 ☆☆☆☆☆

private CacheStrategy getCandidate() {
      ......
//----------上部分為返回快取響應為null的情況-------------

//----------下部分為判斷快取時間是否失效邏輯-------------

      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

       //快取未過期的情況
      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());
      }


      //快取過期的情況
      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);
    }

此方法的下半部分主要在判斷快取響應Response,這部分的處理邏輯與第二大點開頭講解的圖片快取機制原理相對應,可謂是快取機制的精髓!同樣將其分成兩種不同情況進行處理(我註釋的兩個if語句塊):

快取未過期的情況

return new CacheStrategy(null, builder.build());

檢視其返回結果可知,若快取未過期,在建立CacheStrategy類時,網路請求物件為null,將其快取資料傳入,即代表此請求無需請求網路,重複利用快取資料即可。

快取已過期的情況(邏輯步驟)

  • (a)首先判斷Etag欄位是否為null,若不為null,則攜帶引數If-None-Match此引數需與伺服器校驗本地過期的快取是否需要更新,而伺服器通過比較客戶端與自身的這些欄位標識來判斷是否需要通知客戶端進行快取更新。

  • (b)Etag欄位為null,則繼而判斷lastModified欄位是否為null,若不為null,則攜帶引數If-Modified-Since,理由同上。

  • (c)lastModified欄位為null,則繼而判斷servedDate欄位是否為null,若不為null,則攜帶引數If-Modified-Since,這裡快取機制提供了三種欄位去進行判斷,因為並不是每個響應資源的欄位中包含這些資料。

  • (d) 若快取中以上欄位皆為null,則意味著此快取資源過期且無使用價值,還是需要請求網路資源獲取最新資源,則建立CacheStrategy類時,快取響應物件為null。

return new CacheStrategy(request, null);
  • (e)若以上三種欄位有其一不為null,則將網路請求頭重新進行封裝,即在現有的欄位上增加了新的攜帶引數,最終建立CacheStrategy類,傳入新封裝好的網路請求引數request、現有的快取響應response。

4. 小結

以上就是快取策略類的詳細講解,其中快取機制的精髓就在其類的邏輯判斷方法中 —— getCandidate(),重點放在快取機制的原理圖解和此方法的解析!

三. 細節補充 —— Cache 和 DiskLruCache

以上內容通過詳細分析快取攔截器CacheInterceptor類的 intercept方法和 CacheStrategy 快取策略類的分析,已經對Okhttp3框架的快取機制的整體流程、重點類瞭解漸深,這裡再補充一點細節,有關CacheDiskLruCache類的介紹。

1. Cache類初識

首先思緒還是回到最初攔截器的建立程式碼,如下:

【RealCall 類】
interceptors.add(new CacheInterceptor(client.internalCache()));

這裡傳入CacheInterceptor類構造方法的引數是:

【OkHttpClient 類】
  InternalCache internalCache() {
    return cache != null ? cache.internalCache : internalCache;
  }

主要邏輯是判斷cache(此變數代表當前程式可以指定的cache)是否為空,不為空則返回cache的內部快取,為空則返回OkHttpClient 的內部快取。而這個變數cache是Cache類,檢視起部分原始碼:

2. Cache類介紹

【Cache 類】
public final class Cache implements Closeable, Flushable {
  private static final int VERSION = 201105;
  private static final int ENTRY_METADATA = 0;
  private static final int ENTRY_BODY = 1;
  private static final int ENTRY_COUNT = 2;

  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);
    }

    ......
      private final DiskLruCache cache;
}   

檢視其原始碼可見cache.internalCacheCache 類的內部類,但是需要注意的是Cache最終呼叫的是DiskLruCache 類,Cache 類只不過是它的封裝,呼叫Cache 類來對本地快取資料進行修改、刪除等操作。

3. 操作本地快取

繼續來探究操作本地快取的問題,當我們呼叫Okhttp3框架api往本地快取中新增資料時,CacheInterceptor類的maybeCache 方法,原始碼如下:

  private CacheRequest maybeCache(Response userResponse, Request networkRequest,
      InternalCache responseCache) throws IOException {
    if (responseCache == null) return null;

    // Should we cache this response for this request?
    if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          responseCache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
      return null;
    }

    // Offer this request to the cache.
    return responseCache.put(userResponse);
  }
【InternalCache 介面】
public interface InternalCache {
  Response get(Request request) throws IOException;

  CacheRequest put(Response response) throws IOException;
  ......
  }

這裡操作本地快取資料最終呼叫的是responseCache物件的put 方法,檢視其方法可知是InternalCache介面中的一個方法,而它具體實現物件便是Cache類,檢視Cache類的put 方法:

【Cache 類】
private CacheRequest put(Response response) {
    String requestMethod = response.request().method();
    if (HttpMethod.invalidatesCache(response.request().method())) {
      try {
        remove(response.request());
      } catch (IOException ignored) {
      }
      return null;
    }
    if (!requestMethod.equals("GET")) {
      return null;
    }

    if (HttpHeaders.hasVaryAll(response)) {
      return null;
    }
    Entry entry = new Entry(response);
    DiskLruCache.Editor editor = null;
    try {
      editor = cache.edit(urlToKey(response.request()));
      if (editor == null) {
        return null;
      }
      entry.writeTo(editor);
      return new CacheRequestImpl(editor);
    } catch (IOException e) {
      abortQuietly(editor);
      return null;
    }
  }

真相大白!Cache類的put 方法最終呼叫的還是DiskLruCache類,即它才是操作本地快取的底層物件,此方法就是往本地檔案中寫入快取資料,首先if判斷當前的請求方式是否支援,如果支援的話先移除對應請求的資料,避免重複;然後通過DiskLruCache類獲取可書寫Editor 物件,然後會根據當前請求的URL做一個MD5的加密,生成的本地快取名字就是一串字元,如下圖所示,獲取此檔案後,呼叫Entry的writeTo 方法寫入資料即可結束。此方法原始碼如下:

【Entry 類】
public void writeTo(DiskLruCache.Editor editor) throws IOException {
      BufferedSink sink = Okio.buffer(editor.newSink(ENTRY_METADATA));

      sink.writeUtf8(url)
          .writeByte('\n');
      sink.writeUtf8(requestMethod)
          .writeByte('\n');
      sink.writeDecimalLong(varyHeaders.size())
          .writeByte('\n');
      for (int i = 0, size = varyHeaders.size(); i < size; i++) {
        sink.writeUtf8(varyHeaders.name(i))
            .writeUtf8(": ")
            .writeUtf8(varyHeaders.value(i))
            .writeByte('\n');
      }

      sink.writeUtf8(new StatusLine(protocol, code, message).toString())
          .writeByte('\n');
      sink.writeDecimalLong(responseHeaders.size() + 2)
          .writeByte('\n');
      for (int i = 0, size = respon
      ......
      }

結果圖:
7f4c79817fabaeaa0e909754cfe655e7.0 檔案

這裡寫圖片描述

以上本地快取檔案是測試訪問騰訊網後生成的檔案,很明顯檔名經過了md5加密,證實了以上說法。根據對比以上原始碼及本地快取內容可證實其操作本地快取資料流程。

至於最後的DiskLruCache類,它才是真正管理本地快取目錄檔案操作,相關方法如下,這裡就不再坐一一介紹了,此篇部落格的重點還是在第一、二大點。

這裡寫圖片描述

四. 總結

總體而言,Okhttp3原始碼中有關快取機制重點的類為以下四個,通過這四個類來總結快取機制的核心流程:

  • CacheIntercepter
  • CacheStrategy
  • Cache
  • DiskLruCache

核心流程

通過CacheIntercepter類去攔截請求處理快取相關邏輯,其中使用快取策略器CacheStrategy類來取出與快取相關的資料,若快取中有相應資料則取出,若快取中資料不存在或過期則重新向伺服器發出請求。當前本地有指定快取時,重新請求的資源資料會被同步到當前本地中,涉及到的同步類是CacheDiskLruCache類,以上是OKhttp3快取機制的主要流程概括。

到此為止,有關快取機制的原始碼分析已經結束,文章主要結合Okhttp3框架原始碼和圖片搭配解析,最後有關Cache、DiskLruCache類僅做介紹,並無深入,有興趣的讀者可自行分析研究,關於這一塊我日後再做補充。

這篇文章我寫了快一週了,停停寫寫,原始碼分析一直都不是一個容易事,想寫下來弄清楚更是令人頭疼,最後自己動手繪製原始碼流程圖配合文字講解,總算有個大致的思路。我必須承認這篇文章專業度不夠,可能以上某些知識點講解有誤,但是這是盡我目前所能去解析原始碼到的程度,所以有誤還請指教,共同學習~

tips

剛開始分析原始碼時一定先以整體流程為主,弄清楚之後再從細節(即每個方法的具體實現等)入手!

通常文字講解某些知識點特別是邏輯類流程時會不夠透徹,不妨畫個流程圖,會讓自己的思路更加清晰!

— from lemon Guo

希望對你們有幫助 :)

相關推薦

Okhttp3原始碼 ------ 圖解 快取機制原理實現

快取機制一直以來是一個不可忽視的重要模組,廣泛地被運用到 網頁端和移動端。對於伺服器而言,客戶端的快取很大程度上緩解了它的壓力,更是為使用者帶來了產品快速響應的體驗,擁有很多好處。既然是網路請求,必然與HTTP協議聯絡緊密,不論你是否有這之類的經驗,此篇將會從基

Okhttp3原始碼 ------ 圖解 快取機制原理實現

之前寫的一篇是介紹快取機制的流程和原理,並講解了HTTP協議中快取相關的欄位,徹底瞭解了快取機制原理後實踐了Okhttp3框架的快取實現,即第一篇的內容已經打下了基礎,此篇就從原始碼的角度來解析Okhttp3框架的快取機制的實現。 在分析原始碼之前,提醒大

移動平臺播放器ijkplayer開源框架分析(IOS原始碼)

p_prepare_async_l呼叫stream_open,stream_open中建立了視訊渲染執行緒,該執行緒主要是進行視訊渲染工作,並對視訊進行同步,同步相關邏輯主要在這個執行緒裡面,同步的大概思路就是:有一個絕對時間作為同步起點,然後計算當前幀與上一幀時間差,然後與當前絕對時間基準源比較,如果不到時

SQLite3資料庫在嵌入式應用之三: 日誌功能給出常用命令C/C++常用API

/****************************************************** *SQLite3 比較重要的語句 *******************************************************/ #define LOG_TABLE_NAME "

太坊原始碼深入分析4-- 太坊RPC通訊例項原理程式碼分析

上一節我們試著寫了一個RPC的請求例項,通過分析原始碼知道了RPC服務的建立流程,以及Http RPC server建立過程,Http RPC Client的請求流程。這一節,先分析一下Http RPC server如何處理client的請求。然後再分析一下IPC RPC的處

WPF原始碼分析系列一:剖析WPF模板機制的內部實現

眾所周知,在WPF框架中,Visual類是可以提供渲染(render)支援的最頂層的類,所有視覺化元素(包括UIElement、FrameworkElment、Control等)都直接或間接繼承自Visual類。一個WPF應用的使用者介面上的所有視覺化元素一起組成了一個視覺化樹(visual tree),任何

WPF原始碼分析系列一:剖析WPF模板機制的內部實現

(注:本文是《剖析WPF模板機制的內部實現》系列文章的最後一篇文章,檢視上一篇文章請點這裡) 上一篇文章我們討論了DataTemplate型別的兩個重要變數,ContentControl.ContentTemplate和ContentPresenter.ContentTemplate,這一篇將討論這個型別的另

Mybatis原始碼---重寫一個最簡單的Mybatis架構實現

   前兩篇文章裡,我們實現了一個簡單的Mybatis。只要願意,如果完善了後續的資料庫操作,我們完全可以用它來替換本來的Mybatis。在本篇文章裡,我們要做的是完成我們自定義Mybatis與Spring或SpringBoot整合時的自動配置。首先,我們在來熟悉一下在XML

被標記事務的方法互相呼叫的坑

參考:www.iteye.com/topic/11227… 上一節,主要分析了 被標記為事務的方法互相呼叫,事務失效的原因,思考比較多,這一節主要說說解決方案,思考會少一些。 ####解決方案的核心: 通過代理物件去呼叫方法 1.把方法放到不同的類: 我們需要新建一個介面: public inter

ASP.NET MVC擴充套件非同步Action功能

執行Action方法 對於執行同步Action的SyncMvcHandler,其實現十分簡單而直接:public class SyncMvcHandler : IHttpHandler, IRequiresSessionState { public SyncMvcHandler(

kafka原理實踐spring-kafka生產者原始碼

正文系列目錄 本文目錄 1.kafkaProducer傳送模型2.KafkaTemplate傳送模板3.KafkaProducer  3.1KafkaProducer構造過程  3.2 KafkaProducer傳送資料 ==============正文分割線==================

《Visual C++異常處理機制原理與應用—— C/C++結構化異常處理之try-finally終止處理的使用與原理

在上一篇文章中,我們其實只分析了終止型異常處理程式中正常的執行流程,這種情況的出現其實需要作如下假設: __try塊中的程式碼執行過程中不會引發異常 這部分程式碼不會試圖提前離開__try塊的作用範圍(如包含goto、break、continue、retur

快取伺服器設計與實現

本文講快取中的內容管理–檔案的刪除。 基本原理 快取系統中的檔案,從無到有是被動產生的。初始狀態,快取系統中是空的,請求過來之後,快取會回源取,然後存在本地。而不像web伺服器,檔案是通過其他的手段(傳統的是通過ftp上傳)來建立的,這個建立檔案

圖片快取:ImageCacheImageSdCache

對於圖片資源來說,你不可能讓應用每次獲取的時候都重新到遠端去下載,這樣會浪費資源,但是你又不能讓所有圖片資源都放到記憶體中去(雖然這樣載入會比較快),因為圖片資源往往會佔用很大的記憶體空間,容易導致OOM。那麼如果下載下來的圖片儲存到SDCard中,下次直接從SDCard上去獲取呢?這也是一種做法,我看了

kafka原理實踐spring-kafka消費者原始碼

正文系列目錄 ==============正文分割線===================== 回到頂部一、kafkaConsumer消費者模型 如上圖所示,spring-kafka消費者模型主要流程: 1.容器啟動,輪詢執行消費。 2.kafkaConsumer拉取訊息流程: 1)Fetc

快取中介軟體-快取架構的實現

快取中介軟體-快取架構的實現(下) 前言 快取架構,說白了就是利用各種手段,來實現快取,從而降低伺服器,乃至資料庫的壓力。 這裡把之前提出的快取架構的技術分類放出來: 瀏覽器快取 Cookie LocalStorage SessionStorage CDN快取 負載層快取 Nginx快取模組 Squi

冠狀病毒傳播模擬器的原理實現Python版)【附原始碼

本文摘要: 本文首先會解釋一下到底什麼是"冠狀病毒",以及殺死"冠狀病毒"的方法。然後會利用Python實現一個"冠狀病毒"傳播模擬器,來演示一下為何“不出門“ +“瘋狂建醫院”會間接殺死病毒(動態模擬了從發生疫情,到疫情結束的整個過程)。以及如果控制不好,會有

BlockChain:《Blockchain Gate》聽課筆記——POW機制闡述共識機制的激勵相容設計

BlockChain:《Blockchain Gate》聽課筆記——以POW機制為例闡述共識機制的激勵相容設計       區塊鏈技術通過巧妙的經濟激勵和技術設計,創造了一種新型自由開放系統的協作機制,能夠很好地適應經濟一體化深度發展下大規模多邊協作的技術需求。

linux的原始碼安裝步驟安裝nginx

原始碼安裝步驟: 1、下載 2、檢視原始碼 3、準備編譯環境 4、檢查(依賴,相容),預編譯 – configure 5、編譯 – make 6、安裝 – make ins

uboot中 make xxx_config 的作用make smdk2410_config

mdk nbsp xxx cpu clu samsung uboot 作用 頭文件 1、創建到目標板相關文件的鏈接 ln -s asm-arm asm ln -s arch-s3c24x0 asm-arm/arch ln -s proc-armv asm-arm/pr