OkHttpClient源碼分析(四)—— CacheInterceptor

CacheInterceptor

介紹完緩存以後,如今開始介紹緩存攔截器CacheInterceptor了,一樣也是查看其intercept()方法,這裏邊上片斷代碼邊解析,化整爲零:緩存

@Override public Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;
    ...
}
複製代碼

首先經過判斷緩存對象是否爲null,若是不爲null則根據傳入的Chain對象的request獲取緩存的Response。bash

@Override public Response intercept(Chain chain) throws IOException {
      ...
      CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
      Request networkRequest = strategy.networkRequest;
      Response cacheResponse = strategy.cacheResponse;
      ...
}
複製代碼

建立緩存策略對象CacheStrategy,CacheStrategy內部維護了一個Request和一個Response,該類主要是用於解決到底使用網絡仍是緩存,亦或是二者皆用:服務器

/** 若是不使用網絡,則 networkRequest爲 null */
  public final @Nullable Request networkRequest;
  /** 若是不使用緩存,則 cacheResponse爲 null */
  public final @Nullable Response cacheResponse;
複製代碼

關於緩存策略的建立,咱們查看CacheStrategy的內部類Factory的get()方法:網絡

public CacheStrategy get() {
      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; } 複製代碼

該方法比較簡單,建立CacheStrategy的主要的邏輯是在getCandidate()方法中:ide

private CacheStrategy getCandidate() {
      //找不到緩存,須要網絡請求
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }

      // 若是是https請求且缺乏握手操做,須要網絡請求
      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);
      }
      
      //上述須要網絡請求返回的 CacheStrategy 第一個參數傳入request
        
      //若是緩存是不受影響的,CacheStrategy傳入cacheResponse
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }

      
      //能夠緩存,添加請求頭信息
      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());
      }

      ...
    }

複製代碼

介紹完CacheStrategy對象和建立CacheStrategy的過程後,接着分析intercept()方法:源碼分析

@Override public Response intercept(Chain chain) throws IOException {
    ...
    
    //若是有緩存,更新統計指標, 增長命中率
    if (cache != null) {
      cache.trackResponse(strategy);
    }

    ...
   
    //若是當前沒有網絡且找不到緩存
    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();
    }
    
    ...
}

複製代碼

若是有緩存,經過調用CacheStrategy的trackResponse()方法更新統計指標, 更新網絡請求數和命中緩存數;post

接着經過判斷CacheStrategy的networkRequest和cacheResponse,若是兩者同時爲null,即當前沒有網絡且沒有緩存,則構造一個Response對象並返回,其中狀態碼爲504,並設置了提示的message信息。ui

接着分析:spa

@Override public Response intercept(Chain chain) throws IOException {
    ...
      //若是不使用網絡請求,直接返回緩存的Response
     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 (cacheResponse != null) {
      //服務器返回狀態碼爲HTTP_NOT_MODIFIED(304)
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //使用緩存數據
        Response response = cacheResponse.newBuilder()
             ...
            .build();
            ...
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    
    
    //若是服務器資源已經修改,使用網絡響應返回的最新數據
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
        
    
    //若是有緩存
    if (cache != null) {
      //htpp 頭部有響應體且須要緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      //請求方法不符合可以緩存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          //移除對應的request
          cache.remove(networkRequest);
        } catch (IOException ignored) {
        }
      }
    }

    return response;
  }
複製代碼

當服務器返回狀態碼爲HTTP_NOT_MODIFIED即304時,說明緩存還沒過時或服務器資源沒修改,此時返回緩存;若是服務器資源修改了,則使用網絡響應返回的最新數據構造Response,接着將最新的數據緩存並移除無效的緩存。.net

總結CacheInterceptor主要作的操做:

  1. 從緩存中獲取Reponse對象,賦值給caceResponse,若是找不到緩存則爲null;
  2. 根據當前請求request和caceResponse 構建一個CacheStrategy對象;
  3. CacheStrategy這個策略對象將根據相關規則來決定cacheResponse和Request是否有效,若是無效則分別將cacheResponse和request設置爲null;
  4. 若是request和cacheResponse都爲null,即沒有網絡且沒有緩存,直接返回一個狀態碼爲504的空Respone對象;
  5. 若是resquest爲null而cacheResponse不爲null,即沒有網絡且有緩存,則直接返回cacheResponse對象;
  6. 執行下一個攔截器的intercept()方法進行網絡請求,獲取response;
  7. 若是服務器資源沒有過時(狀態碼304)且存在緩存,則返回緩存;
  8. 若是服務器資源有修改,則將返回的最新數據進行緩存並移除掉無效的緩存,最後返回response對象給上一個攔截器。

下一篇將講解五大攔截器中的最後兩個攔截器, ConnectInterceptor和CallServerInterceptor,感興趣的朋友能夠繼續閱讀:

OkHttpClient源碼分析(五)—— ConnectInterceptor和CallServerInterceptor

相關文章
相關標籤/搜索