okHttp 原理解析(二)

緩存機(jī)制

請求頭緩存設(shè)置

  • Cache-Control 常見的取值有private、public、no-cache、max-age、no-store、默認(rèn)是 private。
    在瀏覽器里面,private 表示客戶端可以緩存,public表示客戶端和服務(wù)器都可以緩存。
  • Last-Modified 服務(wù)器告訴瀏覽器資源的最后修改時(shí)間。
  • If-Modified-Since 客戶端再次請求服務(wù)器時(shí),通過此字段通知服務(wù)器上次服務(wù)器返回的最后修改時(shí)間。
    資源被改動過,則響應(yīng)內(nèi)容返回的狀態(tài)碼是200;資源沒有修改,則響應(yīng)狀態(tài)碼為304,告訴客戶端繼續(xù)使用cache。
  • Etag 服務(wù)響應(yīng)請求時(shí),告訴客戶端當(dāng)前資源在服務(wù)器的唯一標(biāo)識
  • If-None-Match 客戶端再次請求服務(wù)器時(shí),通過此字段通知服務(wù)器上次服務(wù)器返回的數(shù)據(jù)標(biāo)識。
    同修改過返回200,可以使用cache 返回304.

CacheStrategy類

CacheStrategy 根據(jù)輸出的networkRequest和cacheResponse的值是否為null給出不同的策略

networkRequest cacheResponse result 結(jié)果
null null only-if-cached (表明不進(jìn)行網(wǎng)絡(luò)請求,且緩存不存在或者過期,一定會返回503錯誤)
null non-null 不進(jìn)行網(wǎng)絡(luò)請求,直接返回緩存,不請求網(wǎng)絡(luò)
non-null null 需要進(jìn)行網(wǎng)絡(luò)請求,而且緩存不存在或者過去,直接訪問網(wǎng)絡(luò)
non-null non-null Header中包含ETag/Last-Modified標(biāo)簽,需要在滿足條件下請求,還是需要訪問網(wǎng)絡(luò)

Cachestrategy 通過如下方式構(gòu)建

CacheStrategy strategy = new CacheStrategy.Factory(
            now, 
            chain.request(), 
            cacheCandidate)
                .get();
    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();
        //獲取cacheReposne中的header中值
        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);
          }
        }
      }
    }

    public CacheStrategy get() {
      //獲取當(dāng)前的緩存策略
      CacheStrategy candidate = getCandidate();
     //如果是網(wǎng)絡(luò)請求不為null并且請求里面的cacheControl是只用緩存
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        //使用只用緩存的策略
        return new CacheStrategy(null, null);
      }
      return candidate;
    }

    private CacheStrategy getCandidate() {
      //如果沒有緩存響應(yīng),返回一個沒有響應(yīng)的策略
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }
       //如果是https,丟失了握手,返回一個沒有響應(yīng)的策略
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }
     
      // 響應(yīng)不能被緩存
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }
     
      //獲取請求頭里面的CacheControl
      CacheControl requestCaching = request.cacheControl();
      //如果請求里面設(shè)置了不緩存,則不緩存
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }
      //獲取響應(yīng)的年齡
      long ageMillis = cacheResponseAge();
      //獲取上次響應(yīng)刷新的時(shí)間
      long freshMillis = computeFreshnessLifetime();
      //如果請求里面有最大持久時(shí)間要求,則兩者選擇最短時(shí)間的要求
      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      //如果請求里面有最小刷新時(shí)間的限制
      if (requestCaching.minFreshSeconds() != -1) {
         //用請求中的最小更新時(shí)間來更新最小時(shí)間限制
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
      //最大驗(yàn)證時(shí)間
      long maxStaleMillis = 0;
      //響應(yīng)緩存控制器
      CacheControl responseCaching = cacheResponse.cacheControl();
      //如果響應(yīng)(服務(wù)器)那邊不是必須驗(yàn)證并且存在最大驗(yàn)證秒數(shù)
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        //更新最大驗(yàn)證時(shí)間
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
       //響應(yīng)支持緩存
       //持續(xù)時(shí)間+最短刷新時(shí)間<上次刷新時(shí)間+最大驗(yàn)證時(shí)間 則可以緩存
      //現(xiàn)在時(shí)間(now)-已經(jīng)過去的時(shí)間(sent)+可以存活的時(shí)間<最大存活時(shí)間(max-age)
      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\"");
        }
       //緩存響應(yīng)
        return new CacheStrategy(null, builder.build());
      }
    
      //如果想緩存request,必須要滿足一定的條件
      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 {
        //沒有條件則返回一個定期的request
        return new CacheStrategy(request, null); 
      }
      
      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      //返回有條件的緩存request策略
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }

CacheInterceptor 類

負(fù)責(zé)將Request和Response 關(guān)聯(lián)的保存到緩存中。客戶端和服務(wù)器根據(jù)一定的機(jī)制(策略CacheStrategy ),在需要的時(shí)候使用緩存的數(shù)據(jù)作為網(wǎng)絡(luò)響應(yīng),節(jié)省了時(shí)間和寬帶。

 //CacheInterceptor.java
 @Override 
 public Response intercept(Chain chain) throws IOException {
    //如果存在緩存,則從緩存中取出,有可能為null
    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;
     //策略中的響應(yīng)
    Response cacheResponse = strategy.cacheResponse;
     //緩存非空判斷,
    if (cache != null) {
      cache.trackResponse(strategy);
    }
    //緩存策略不為null并且緩存響應(yīng)是null
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }
     //禁止使用網(wǎng)絡(luò)(根據(jù)緩存策略),緩存又無效,直接返回
    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();
    }
     //緩存有效,不使用網(wǎng)絡(luò)
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    //緩存無效,執(zhí)行下一個攔截器
    Response networkResponse = null;
    try {
      networkResponse = chain.proceed(networkRequest);
    } finally {
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
     //本地有緩存,根據(jù)條件選擇使用哪個響應(yīng)
    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();

        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
     //使用網(wǎng)絡(luò)響應(yīng)
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
       //緩存到本地
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
        }
      }
    }

    return response;
  }

大致流程如下:

  1. 如果配置緩存,則從緩存中取一次
  2. 獲取緩存策略
  3. 根據(jù)緩存策略獲取緩存
  4. 沒有網(wǎng)絡(luò)并且緩存為空,直接返回
  5. 沒有網(wǎng)絡(luò),直接根據(jù)緩存的response返回
  6. 執(zhí)行下一個攔截器
  7. 存在緩存,根據(jù)response的相應(yīng)頭選擇緩存
  8. 不存在緩存,直接使用網(wǎng)絡(luò) response
  9. 根據(jù)緩存策略緩存到本地

Cache

public final class Cache implements Closeable, Flushable {
  final InternalCache internalCache = new InternalCache() {
    @Override public @Nullable Response get(Request request) throws IOException {
      return Cache.this.get(request);
    }

    @Override public @Nullable 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);
    }

    @Override public void trackConditionalCacheHit() {
      Cache.this.trackConditionalCacheHit();
    }

    @Override public void trackResponse(CacheStrategy cacheStrategy) {
      Cache.this.trackResponse(cacheStrategy);
    }
  };
  Cache(File directory, long maxSize, FileSystem fileSystem) {
    this.cache = DiskLruCache.create(fileSystem, directory, VERSION, ENTRY_COUNT, maxSize);
  }
}
  DiskLruCache(FileSystem fileSystem, File directory, int appVersion, int valueCount, long maxSize,
      Executor executor) {
    this.fileSystem = fileSystem;
    this.directory = directory;
    this.appVersion = appVersion;
    this.journalFile = new File(directory, JOURNAL_FILE);
    this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
    this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
    this.valueCount = valueCount;
    this.maxSize = maxSize;
    this.executor = executor;
  }

DiskLruCache 內(nèi)部類

Entry 實(shí)際用于存儲的緩存數(shù)據(jù)的實(shí)體類,每一個url對應(yīng)一個Entry實(shí)體。同時(shí),每個Entry對應(yīng)兩個文件,key.1存儲的是Response的headers,key.2文件存儲的是Response的body
Snapshot 一個Entry對象一一對應(yīng)一個Snapshot對象
Editor 編輯entry類的

初始化

DiskLruCache包含三個日志文件,在執(zhí)行任何成員函數(shù)之前,都需要 initialize() 方法先進(jìn)行初始化,雖然都調(diào)用,但整個生命周期只會被執(zhí)行一次。

在執(zhí)行 readJournalLine () 的時(shí)候我們會根據(jù)不同的頭部做出不同的操作

  1. 如果是CLEAN的話,對這個entry的文件長度進(jìn)行更新
  2. 如果是DIRTY,說明這個值正在被操作,還沒有commit,于是給entry分配一個Editor。
  3. 如果是READ,說明這個值被讀過了,什么也不做。
    journal 文件
libcore.io.DiskLruCache // MAGIC
1 // VERSION
100 // appVersion
2 // valueCount 每個entry的 value 數(shù)量

CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
DIRTY 335c4c6028171cfddfbaae1a9c313c52
CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
REMOVE 335c4c6028171cfddfbaae1a9c313c52
DIRTY 1ab96a171faeeee38496d8b330771a7a
CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
READ 335c4c6028171cfddfbaae1a9c313c52
READ 3400330d1dfc7f3f7f4b8d4d803dfcf6

在執(zhí)行 rebuildJournal () 的時(shí)候

  1. 獲取一個寫入流,將lruEntries集合中的Entry對象寫入tmp文件中,根據(jù)Entry的currentEditor的值判斷是CLEAN還是DIRTY,來決定寫入該Entry的key。如果是CLEAN還需要寫入文件的大小bytes。
  2. 把journalFileTmp更名為journalFile
  3. 將journalWriter跟文件綁定,通過它來向journalWrite寫入數(shù)據(jù),最后設(shè)置一些屬性即可。

其實(shí) rebuild 操作是以lruEntries為準(zhǔn),把DIRTY和CLEAN的操作都寫回到j(luò)ournal中。其實(shí)這個操作沒有改動真正的value,只不過重寫了一些事務(wù)的記錄。事實(shí)上,lruEntries和journal文件共同確定了cache數(shù)據(jù)的有效性。lruEntries是索引,journal是歸檔。

總結(jié):

  • 通過LinkedHashMap實(shí)現(xiàn)LRU替換
  • 通過本地維護(hù)Cache操作日志保證Cache原子性與可用性,同時(shí)為防止日志過分膨脹定時(shí)執(zhí)行日志精簡。
  • 每一個Cache項(xiàng)對應(yīng)兩個狀態(tài)副本:DIRTY,CLEAN。CLEAN表示當(dāng)前可用的Cache。外部訪問到cache快照均為CLEAN狀態(tài);DIRTY為編輯狀態(tài)的cache。由于更新和創(chuàng)新都只操作DIRTY狀態(tài)的副本,實(shí)現(xiàn)了讀和寫的分離。
  • 每一個url請求cache有四個文件。首先是兩個狀態(tài)(DIRY,CLEAN),而每個狀態(tài)又對應(yīng)兩個文件:一個(key.0, key.0.tmp)文件對應(yīng)存儲meta數(shù)據(jù),一個(key.1, key.1.tmp)文件存儲body數(shù)據(jù)。

CallServerInterceptor

連接與請求

OkHttp 中,ConnectionSpec用于描述HTTP數(shù)據(jù)經(jīng)由socket時(shí)的socket連接配置。由 OkHttpClient 管理。

還提供了ConnectionSpecSelector,用以從ConnectionSpec幾個中選擇與SSLSocket匹配的ConnectionSpec,并對SSLSocket做配置操作。

在RetryAndFollowUpInterceptor這個攔截器中,需要創(chuàng)建Address,從OkHttpClient中獲取ConnectionSpec集合,交給Address配置。
接著在ConnectInterceptor 這個攔截器中,newExchange() -> find() -> findHealthyConnection() -> findConnection() -> connect() 的時(shí)候,ConnectionSpec集合就會從Address中取出來,用于構(gòu)建連接過程。

接著往下是 connect() -> establishProtocol() -> connectTls() -> configureSecureSocket() ->OkHttpClient.apply() -> supoortedSpec() ,這就是重新構(gòu)建一個兼容的 ConnectionSpec,并配置到 SSLSocket 上

請求頭
  @Override public void writeRequestHeaders(Request request) throws IOException {
    String requestLine = RequestLine.get(
        request, streamAllocation.connection().route().proxy().type());
    writeRequest(request.headers(), requestLine);
  }
 
  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }
請求體
@Override 
public Sink createRequestBody(Request request, long contentLength) {
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      // Stream a request body of unknown length.
      return newChunkedSink();
    }
    ...
}
  private final class ChunkedSink implements Sink {
    private final ForwardingTimeout timeout = new ForwardingTimeout(sink.timeout());
    private boolean closed;

    ChunkedSink() {
    }

    @Override public Timeout timeout() {
      return timeout;
    }

    @Override public void write(Buffer source, long byteCount) throws IOException {
      if (closed) throw new IllegalStateException("closed");
      if (byteCount == 0) return;

      sink.writeHexadecimalUnsignedLong(byteCount);
      sink.writeUtf8("\r\n");
      sink.write(source, byteCount);
      sink.writeUtf8("\r\n");
    }

    @Override public synchronized void flush() throws IOException {
      if (closed) return; // Don't throw; this stream might have been closed on the caller's behalf.
      sink.flush();
    }

    @Override public synchronized void close() throws IOException {
      if (closed) return;
      closed = true;
      sink.writeUtf8("0\r\n\r\n");
      detachTimeout(timeout);
      state = STATE_READ_RESPONSE_HEADERS;
    }
  }

寫完請求頭和請求體會調(diào)用 sink.flush()

接下來是讀取相應(yīng)頭和響應(yīng)體

  @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
      throw new IllegalStateException("state: " + state);
    }

    try {
      StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());

      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      // Provide more context if the server ends the stream before sending a response.
      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
      exception.initCause(e);
      throw exception;
    }
  }

  @Override public ResponseBody openResponseBody(Response response) throws IOException {
    Source source = getTransferStream(response);
    return new RealResponseBody(response.headers(), Okio.buffer(source));
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容