OKhttp攔截器詳解1—RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor

前言

在上一篇文章中,我們梳理了一遍Okhttp的源碼,初步了解了基本執(zhí)行流程,是一篇總覽全局的文章。拋磚引玉,通過上一篇文章我們搞明白了Okhttp的基本工作原理,也知道Okhttp網絡請求的真正執(zhí)行是通過攔截器鏈關聯(lián)的給個攔截器進行處理的,每個攔截器負責不同的功能,下面我們就來一一分析每個攔截器。

攔截器責任鏈

OKHttp最核心的工作是在getResponseWithInterceptorChain()中進行的,在分析之前,我們先來了解什么事責任鏈模式。

責任鏈模式

責任鏈顧名思義就是由一系列的負責者構成的一個鏈條。

為請求創(chuàng)建了一個接受者對象的鏈。這種模式給予請求的類型,對請求的發(fā)送者和接收者進行解耦。在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那么它會吧相同的請求傳給下一個接收者,以此類推。

攔截器流程

先看下RealCallgetResponseWithInterceptorChain(),攔截器的添加和連接器鏈的執(zhí)行都在此方法中。

 @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // 創(chuàng)建一系列攔截器
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)
    val chain = RealInterceptorChain(
      call = this,
      interceptors = interceptors,
      index = 0,
      exchange = null,
      request = originalRequest,
      connectTimeoutMillis = client.connectTimeoutMillis,
      readTimeoutMillis = client.readTimeoutMillis,
      writeTimeoutMillis = client.writeTimeoutMillis
    )
     ...
      //攔截器鏈的執(zhí)行
      val response = chain.proceed(originalRequest)
     ...
      return response
     ...
  }

getResponseWithInterceptorChain()中攔截器鏈流程:

Okhttp責任鏈流程.jpg

回顧下上一篇介紹的Okhttp五大默認攔截器:

  1. RetryAndFollowUpInterceptor第一個接觸到請求,最后接觸到響應(U型調用)的默認攔截器;負責處理錯誤重試和重定向 。
  2. BridgeInterceptor:補全請求,并對響應進行額外處理。
  3. CacheInterceptor:請求前查詢緩存,獲得響應并判斷是否需要緩存。
  4. ConnectInterceptor:與服務器完成TCP連接。
  5. CallServerInterceptor: 與服務器通信;封裝請求數據與解析響應數據(如:HTTP報文)。

RetryAndFollowUpInterceptor(重試、重定向)

如果沒有添加應用攔截器,那么第一個攔截器就是RetryAndFollowUpInterceptor,主要作用就是:連接失敗后進行重試、對請求結果跟進后進行重定向。接著看下它的Intercept方法:

@Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    var followUpCount = 0
    var priorResponse: Response? = null
    var newRoutePlanner = true
    var recoveredFailures = listOf<IOException>()
    while (true) {
      //準備連接
      call.enterNetworkInterceptorExchange(request, newRoutePlanner, chain)

      var response: Response
      var closeActiveExchange = true
      try {
        if (call.isCanceled()) {
          throw IOException("Canceled")
        }

        try {
          //繼續(xù)執(zhí)行下一個Interceptor
          response = realChain.proceed(request)
          newRoutePlanner = true
        } catch (e: IOException) {
          // 嘗試與服務器通信失敗。請求可能已經發(fā)送
          if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
            throw e.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e
          }
          newRoutePlanner = false
          continue
        }

        // 清除下游攔截器的其他請求標頭、cookie 等
        response = response.newBuilder()
          .request(request)
          .priorResponse(priorResponse?.stripBody())
          .build()

        val exchange = call.interceptorScopedExchange
         //跟進結果,主要作用是根據響應碼處理請求,返回Request不為空時則進行重定向處理-拿到重定向的request
        val followUp = followUpRequest(response, exchange)
        
       //followUp為空,不需要重試,直接返回
        if (followUp == null) {
          if (exchange != null && exchange.isDuplex) {
            call.timeoutEarlyExit()
          }
          closeActiveExchange = false
          return response
        }

        val followUpBody = followUp.body
        if (followUpBody != null && followUpBody.isOneShot()) {
          closeActiveExchange = false
          return response
        }

        response.body.closeQuietly()
       //最多重試20次
        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw ProtocolException("Too many follow-up requests: $followUpCount")
        }
       //賦值,以便再次請求
        request = followUp
        priorResponse = response
      } finally {
       // 請求沒成功,釋放資源。
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

重試

可以看到請求階段發(fā)生了IOException(老版本還會捕獲RouteException),會通過recover方法判斷是否能夠進行重試,若返回true,則表示允許重試。

private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
  ): Boolean {
    // 應用層禁止重試,就不重試
    if (!client.retryOnConnectionFailure) return false

    // 不能再次發(fā)送請求,就不重試(requestSendStarted只在http2的io異常中為true,先不管http2)
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false

    // 發(fā)生的異常是致命的,就不重試
    if (!isRecoverable(e, requestSendStarted)) return false

    // 沒有路由可以嘗試,就不重試
    if (!call.retryAfterFailure()) return false

    return true
  }

接下來看一下isRecoverable中判斷了哪些異常。

private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
    // 出現(xiàn)協(xié)議異常,不能重試
    if (e is ProtocolException) {
      return false
    }

    // 如果發(fā)生中斷( requestSendStarted認為它一直為false(不管http2)),不要恢復;但如果連接到路由器超時(socket超時),判定可以重試
    if (e is InterruptedIOException) {
      return e is SocketTimeoutException && !requestSendStarted
    }

    //SSL握手異常中,證書出現(xiàn)問題,不能重試
    if (e is SSLHandshakeException) {
      if (e.cause is CertificateException) {
        return false
      }
    }
  //SSL握手未授權異常 不能重試
    if (e is SSLPeerUnverifiedException) {
      return false
    }
    return true
  }
  1. 協(xié)議異常:例如你的請求或者服務器的響應本身就存在問題,沒有按照http協(xié)議來定義數據,直接無法重試。
  2. 超時異常:可能由于網絡波動造成了Socket管道的超時,如果有下一個路由的話,嘗試下一個路由。
  3. SSL證書異常/SSL驗證失敗異常:證書驗證失敗或者缺少證書,無法重試。

簡單總結一下,首先在用戶未禁止重試的前提下,如果出現(xiàn)了某些異常,則會在isRecoverable中進行判斷,進過上面一系列判斷后,如果允許進行重試,就會再檢查當前是否有可用路由進行連接重試。比如DNS對域名解析后可能會返回多個IP,在一個IP失敗后,嘗試另一個IP進行重試。

重定向

如果realChain.proceed沒有發(fā)生異常,返回了結果response,就會使用followUpRequest方法跟進結果并構建重定向request。 如果不用跟進處理(例如響應碼是200),則返回null??聪耭ollowUpRequest方法:

  @Throws(IOException::class)
  private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
    val route = exchange?.connection?.route()
    val responseCode = userResponse.code
    val method = userResponse.request.method
    when (responseCode) {
       // 407 客戶端使用了HTTP代理服務器,在請求頭中添加 “Proxy-Authorization”,讓代理服務器授權
      HTTP_PROXY_AUTH -> {
        val selectedProxy = route!!.proxy
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
        }
        return client.proxyAuthenticator.authenticate(route, userResponse)
      }
       // 401 需要身份驗證 有些服務器接口需要驗證使用者身份 在請求頭中添加 “Authorization” 
      HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
      //308 永久重定向 ,307 臨時重定向,300,301,302,303 
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
        return buildRedirectRequest(userResponse, method)
      }
       // 408 客戶端請求超時 
      HTTP_CLIENT_TIMEOUT -> {
           // 408 算是連接失敗了,所以判斷用戶是不是允許重試
        if (!client.retryOnConnectionFailure) {
          return null
        }
        val requestBody = userResponse.request.body
        if (requestBody != null && requestBody.isOneShot()) {
          return null
        }
        val priorResponse = userResponse.priorResponse
         // 如果是本身這次的響應就是重新請求的產物同時上一次之所以重請求還是因為408,那我們這次不再重請求了
        if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
          return null
        }
        
         // 如果服務器告訴我們了 Retry-After 多久后重試,那框架不管了。
        if (retryAfter(userResponse, 0) > 0) {
          return null
        }
        return userResponse.request
      }
         // 503 服務不可用 和408差不多,但是只在服務器告訴你 Retry-After:0(意思就是立即重試) 才重請求
      HTTP_UNAVAILABLE -> {
        val priorResponse = userResponse.priorResponse
        if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
          return null
        }
        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          return userResponse.request
        }
        return null
      }
      HTTP_MISDIRECTED_REQUEST -> {
        val requestBody = userResponse.request.body
        if (requestBody != null && requestBody.isOneShot()) {
          return null
        }
        if (exchange == null || !exchange.isCoalescedConnection) {
          return null
        }
        exchange.connection.noCoalescedConnections()
        return userResponse.request
      }

      else -> return null
    }
  }

主要就是根據響應碼判斷如果需要重定向,如果此方法返回空,那就表示不需要再重定向了,直接返回響應;但是如果返回非空,那就要重新請求返回的Request,但是需要注意的是,followup`在攔截器中定義的最大次數為20次。

總結

  1. 如果沒有添加應用攔截器,那么RetryAndFollowUpInterceptor就是第一個攔截器,主要功能是判斷是否需要重試和重定向。
  2. 如果請求階段發(fā)生了IOException,就會通過recover方法判斷是進行連接重試。
  3. 重定向發(fā)生在重試的判定之后,如果不滿足重試的條件,還需要進一步調用followUpRequest根據Response 的響應碼(當然,如果直接請求失敗,Response都不存在就會拋出異常)判斷是否重定向;followup最大發(fā)生次數20次。

BridgeInterceptor(橋接攔截器)

BridgeInterceptor,連接應用程序和服務器的橋梁,我們發(fā)出的請求會經過它的處理才能發(fā)給服務器,比如設置請求內容的長度,編碼,gzip壓縮,cookie等;獲取響應后保存cookie、解壓等操作。相對比較簡單。

首先,chain.proceed()執(zhí)行前,對請求頭進行補全,補全請求頭如下:

請求頭 說明
Content-Type 請求體類型,如:application/x-www-form-urlencoded
Content-Length/Transfer-Encoding 請求體解析方式
Host 請求的主機站點
Connection: Keep-Alive 保持長連接
Accept-Encoding: gzip 接受響應支持gzip壓縮
Cookie cookie身份辨別
User-Agent 請求的用戶信息,如:操作系統(tǒng)、瀏覽器等

在補全了請求頭后交給下一個攔截器處理,得到響應后,主要干兩件事情:

  1. 先把響應header中的cookie存入cookieJar(如果有),在下次請求則會讀取對應的數據設置進入請求頭,默認的CookieJar不提供實現(xiàn),需要我們在初始化OkhttpClient時配置我們自己的cookieJar。
  2. 如果使用gzip返回的數據,則使用GzipSource包裝便于解析。

總結

  1. 對用戶構建的Request進行添加或者刪除相關頭部信息,以轉化成能夠真正進行網絡請求的Request
  2. 將符合網絡請求規(guī)范的Request交給下一個攔截器處理,并獲取Response
  3. 如果響應體經過了gzip壓縮,那就需要解壓,再構建成用戶可用的Response并返回。

CacheInterceptor(緩存攔截器)

CacheInterceptor,在發(fā)出請求前,先判斷是否命中緩存,如果命中則可以不請求,直接使用緩存的響應(默認只會對Get請求進行緩存);如果未命中則進行網絡請求,并將結果緩存,等待下次請求被命中。

先來看下CacheInterceptorintercept方法代碼:

 @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    // 用request的url 從緩存中獲取Response作為候選(CacheStrategy決定是否使用)
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()
    // 根據request、候選Response 獲取緩存策略。
    // 緩存策略 將決定是否使用緩存:strategy.networkRequest為null,不適用網絡;
    // strategy.cacheResponse為null,不使用緩存
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
   // 根據緩存策略更新統(tǒng)計指標:請求次數、網絡請求次數、使用緩存次數
    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE

    if (cacheCandidate != null && cacheResponse == null) {
      // The cache candidate wasn't applicable. Close it.
      cacheCandidate.body.closeQuietly()
    }

    // 網絡請求、緩存 都不能用,就返回504
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)
          .message("Unsatisfiable Request (only-if-cached)")
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // 如果不用網絡,cacheResponse肯定不為空了,那么就使用緩存了,結束了
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(cacheResponse.stripBody())
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }
   //到這里,networkRequest != null (cacheResponse可能為null,也可能!null)
   //networkRequest != null,就要進行網絡請求了, 所以攔截器鏈就繼續(xù)往下處理了
    var networkResponse: Response? = 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) {
        cacheCandidate.body.closeQuietly()
      }
    }

    // 如果網絡請求返回304,表示服務端資源沒有修改,那么就結合網絡響應和緩存響應,然后更新緩存->返回->結束
    if (cacheResponse != null) {
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        val response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers, networkResponse.headers))//結合header
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis)//請求事件
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis)//接受事件
            .cacheResponse(cacheResponse.stripBody())
            .networkResponse(networkResponse.stripBody())
            .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.also {
          listener.cacheHit(call, it)
        }
      } else {
        //如果是非304,說明服務端資源有更新,就關閉緩存body
        cacheResponse.body.closeQuietly()
      }
    }

    val response = networkResponse!!.newBuilder()
        .cacheResponse(cacheResponse?.stripBody())
        .networkResponse(networkResponse.stripBody())
        .build()

    if (cache != null) {
      //網絡響應可緩存(請求和響應的頭Cache-Control都不是'no-store')
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // 寫入緩存
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }
//Okhttp默認只會對get請求進行緩存,因為get請求的數據一般是比較持久的,而post一般是交互操作,沒太大意義進行緩存
   //不是get請求就移除緩存
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }
    return response
  }

大概梳理一下步驟:

  1. 從緩存中獲取對應請求的響應緩存

  2. 創(chuàng)建CacheStrategy,創(chuàng)建時會判斷是否能夠使用緩存,在CacheStrategy中有兩個成員:networkRequestcacheResponse。他們有如下組合進行判斷:

    networkRequest cacheResponse 說明
    Null Null 網絡請求、緩存 都不能用,okhttp直接返回504
    Null Not Null 直接使用緩存
    Not Null Null 向服務器發(fā)起請求
    Not Null Not Null 向服務器發(fā)起請求,若得到響應碼為304(無修改),則更新緩存響應并返回
  3. 交給下一個責任鏈繼續(xù)處理

  4. 后續(xù)工作,返回304則用緩存的響應;否則使用網絡響應并緩存本次響應(只緩存Get請求的響應)

緩存攔截器中判斷使用緩存或者請求服務器都是通過CacheStrategy判斷。分析CacheStrategy前我們先來了解下Http的緩存機制,以便更好的理解:

首次請求:

1726b5f779033daf_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.jpg

第二次請求:
1726b5f779755ba1_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.jpg

上面兩張圖是對http緩存機制的一個總結:根據 緩存是否過期過期后是否有修改 來決定 請求是否使用緩存。詳細內容可看這篇文章: 徹底弄懂HTTP緩存機制及原理

回過頭我們再來看CacheStrategy,它的生成代碼看起來很簡單,只有一行代碼:

 val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()

把請求request、獲選緩存cacheCandidate傳入工廠類Factory,然后調用compute()方法。

  class Factory(
    private val nowMillis: Long,
    internal val request: Request,
    private val cacheResponse: Response?
  ) {
    init {
      if (cacheResponse != null) {
        /*獲取候選緩存的請求時間、響應時間,從header中獲取 過期時間、修改時間、資源標記等(如果有)*/
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
        val headers = cacheResponse.headers
        for (i in 0 until headers.size) {
          val fieldName = headers.name(i)
          val value = headers.value(i)
          when {
            fieldName.equals("Date", ignoreCase = true) -> {
              servedDate = value.toHttpDateOrNull()
              servedDateString = value
            }
            fieldName.equals("Expires", ignoreCase = true) -> {
              expires = value.toHttpDateOrNull()
            }
            fieldName.equals("Last-Modified", ignoreCase = true) -> {
              lastModified = value.toHttpDateOrNull()
              lastModifiedString = value
            }
            fieldName.equals("ETag", ignoreCase = true) -> {
              etag = value
            }
            fieldName.equals("Age", ignoreCase = true) -> {
              ageSeconds = value.toNonNegativeInt(-1)
            }
          }
        }
      }
    }
    
     /** Returns a strategy to satisfy [request] using [cacheResponse]. */
    fun compute(): CacheStrategy {
      val candidate = computeCandidate()

      // We're forbidden from using the network and the cache is insufficient.
      if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
        return CacheStrategy(null, null)
      }

      return candidate
    }
  }

Factory構造方法內,獲取了候選響應的請求時間、響應時間、過期時長、修改時間、資源標記等。

compute方法內部先調用了computeCandidate()獲取到緩存策略實例,先跟進到computeCandidate方法中:

/** Returns a strategy to use assuming the request can use the network. */
    private fun computeCandidate(): CacheStrategy {
      // 沒有緩存——進行網絡請求
      if (cacheResponse == null) {
        return CacheStrategy(request, null)
      }
      //https請求,但沒有握手——進行網絡請求
      if (request.isHttps && cacheResponse.handshake == null) {
        return CacheStrategy(request, null)
      }

      // 網絡響應不可緩存(請求或響應頭Cache-Control是'no-store')——進行網絡請求
      if (!isCacheable(cacheResponse, request)) {
        return CacheStrategy(request, null)
      }
   //請求頭Cache-Control是no-cache或者請求頭有"If-Modified-Since"或"If-None-Match"——進行網絡請求
    //意思是不使用緩存或者請求手動添加了頭部"If-Modified-Since"或"If-None-Match"
      val requestCaching = request.cacheControl
      if (requestCaching.noCache || hasConditions(request)) {
        return CacheStrategy(request, null)
      }
   
      val responseCaching = cacheResponse.cacheControl
     //緩存的年齡
      val ageMillis = cacheResponseAge()
      //緩存的有效期
      var freshMillis = computeFreshnessLifetime()
     //比較請求頭里有效期,取較小值
      if (requestCaching.maxAgeSeconds != -1) {
        freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
      }
      //可接受緩存的最小剩余時間(min-fresh表示客戶端不愿意接收剩余有效期<=min-fresh的緩存)
      var minFreshMillis: Long = 0
      if (requestCaching.minFreshSeconds != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
      }
      //可接受緩存的最大過期時間(max-stale指令表示了客戶端愿意接收一個過期了的緩存,例如:過期了1小時還可以用)
      var maxStaleMillis: Long = 0
      // 第一個判斷:是否要求必須去服務器驗證資源狀態(tài)
      // 第二個判斷:獲取max-stale值,如果不等于-1,說明緩存過期后還能使用指定的時長
      if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
      }
//如果響應頭沒有要求忽略本地緩存并且整合后的緩存年齡小于整合后的過期時間,那么緩存就可以用
      if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        val builder = cacheResponse.newBuilder()
          //沒有滿足“可接受的最小剩余有效時間”,加個110警告
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"")
        }
        val oneDayMillis = 24 * 60 * 60 * 1000L
        //isFreshnessLifetimeHeuristic表示沒有過期時間且緩存的年齡大于一天,就加個113警告
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"")
        }
        return CacheStrategy(null, builder.build())
      }

      //到這里,說明緩存是過期的
      //然后找緩存里的Etag、lastModified、servedDate
      val conditionName: String
      val conditionValue: String?
      when {
        etag != null -> {
          conditionName = "If-None-Match"
          conditionValue = etag
        }

        lastModified != null -> {
          conditionName = "If-Modified-Since"
          conditionValue = lastModifiedString
        }

        servedDate != null -> {
          conditionName = "If-Modified-Since"
          conditionValue = servedDateString
        }
        //都沒有,就執(zhí)行常規(guī)網絡請求
        else -> return CacheStrategy(request, null) // No condition! Make a regular request.
      }
   // 如果有,就添加到網絡請求的頭部
      val conditionalRequestHeaders = request.headers.newBuilder()
      conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)

      val conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build()
      //conditionalRequest(條件網絡請求):有緩存但過期了,去請求網絡詢問服務器是否能用,能用側返回304,不能則正常執(zhí)行網絡請求
      return CacheStrategy(conditionalRequest, cacheResponse)
    }

同樣經過一系列判斷:

  1. 沒有緩存、https請求但沒有握手、手動設置不緩存、忽略緩存或者手動配置緩存過期,都是直接進行網絡請求。
  2. 1中都不滿足時,如果緩存沒有過期,則使用緩存(可能會添加警告)。
  3. 1中都不滿足時,如果緩存過期了,但響應頭有Etag,Last-Modified,Date,就添加這些header進行條件網絡請求。
  4. 1中都不滿足時,如果緩存過期了,且響應頭沒有設置Etag,Last-Modified,Date,就進行常規(guī)網絡請求。

回頭接著再看compute()方法:

  /** Returns a strategy to satisfy [request] using [cacheResponse]. */
    fun compute(): CacheStrategy {
      val candidate = computeCandidate()
      // We're forbidden from using the network and the cache is insufficient.
      if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
        return CacheStrategy(null, null)
      }
      return candidate
    }
  }

computeCandidate()獲取到緩存策略候選后,又進行了一個判斷:使用網絡請求 但是 原請求配置了只能使用緩存,按照上面的分析,此時即使有緩存,也是過期的緩存,所以又new了實例,兩個值都為null。

到這里okhttp的緩存機制源碼就看完了。okhttp的緩存機制是符合開頭http的緩存機制那兩張圖的,只是增加很多細節(jié)判斷。

另外注意,緩存的讀寫是通過 InternalCache完成的。InternalCache是在創(chuàng)建CacheInterceptor實例時 用client.internalCache()作為參數傳入。而InternalCache是okhttp內部使用,類似一個代理,InternalCache的實例是 類的屬性。Cache 是我們初始化 OkHttpClient時傳入的。所以如果沒有傳入Cache實例是沒有緩存功能的。

 val client = OkHttpClient.Builder()
                .cache(Cache(getExternalCacheDir(),500 * 1024 * 1024))
                .build()

緩存的增刪改查,Cache是通過okhttp內部的DiskLruCache實現(xiàn)的,原理和jakewharton的DiskLruCache是一致的,這里就不再敘述。

結語

本篇文章講解了三個攔截器工作原理,分別是:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor。由于篇幅原因,剩下兩個攔截器:ConnectInterceptor、CallServerInterceptor,放在下一篇進行講解。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容