日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

作者: jimuzz

來源:https://www.cnblogs.com/jimuzz/p/14557234.html

前言

之前我們結(jié)合設(shè)計(jì)模式簡單說了下 OkHttp 的大體流程,今天就繼續(xù)說說它的核心部分—— 攔截器 。

因?yàn)閿r截器組成的鏈其實(shí)是完成了網(wǎng)絡(luò)通信的整個(gè)流程,所以我們今天就從這個(gè)角度說說各攔截器的功能。

首先,做一下簡單回顧,從
getResponseWithInterceptorChain 方法開始。

簡單回顧(getResponseWithInterceptorChain)

internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    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(
        interceptors = interceptors
        //...
    )

    val response = chain.proceed(originalRequest)
  }

這些攔截器會(huì)形成一條鏈,組織了請求接口的所有工作。

從網(wǎng)絡(luò)請求過程看OkHttp攔截器

 

以上為上節(jié)內(nèi)容,不了解的朋友可以返回上一篇文章看看。

假如我來設(shè)計(jì)攔截器

先拋開攔截器的這些概念不談,我們回顧下 網(wǎng)絡(luò)通信過程 ,看看實(shí)現(xiàn)一個(gè)網(wǎng)絡(luò)框架至少要有哪些功能。

請求過程
響應(yīng)過程

而之前說過攔截器的基本代碼格式是這樣:

override fun intercept(chain: Interceptor.Chain): Response {
    //做事情A

    response = realChain.proceed(request)

    //做事情B
  }

也就是分為 請求前工作,請求傳遞,獲取響應(yīng)后工作 三部分。

那我們試試能不能把上面的功能分一分,設(shè)計(jì)出幾個(gè)攔截器?

  • 攔截器1 : 處理請求前的 請求報(bào)文封裝 ,處理響應(yīng)后的 響應(yīng)報(bào)文分析

誒,不錯(cuò)吧,攔截器1就用來處理 請求報(bào)文和響應(yīng)報(bào)文的一些封裝和解析工作。就叫它封裝攔截器吧。

  • 攔截器2 : 處理請求前的 建立TCP連接

肯定需要一個(gè)攔截器用來建立TCP連接,但是響應(yīng)后好像沒什么需要做連接方面的工作了?那就先這樣,叫它連接攔截器吧。

  • 攔截器3 :處理請求前的 數(shù)據(jù)請求(寫到數(shù)據(jù)流中) 處理響應(yīng)后的 數(shù)據(jù)獲取(從數(shù)據(jù)流拿數(shù)據(jù))

這個(gè)攔截器就負(fù)責(zé)TCP連接后的 I/O操作,也就是從流中讀取和獲取數(shù)據(jù)。就叫它 數(shù)據(jù)IO攔截器 吧。

好了,三個(gè)攔截器好像足夠了,我得意滿滿的偷看了一眼okhttp攔截器代碼,7個(gè)???我去。。

那再思考思考 ...,還有什么情況沒考慮到呢?比如失敗重試?返回301重定向?緩存的使用?用戶自己對請求的統(tǒng)一處理?

所以又可以模擬出幾個(gè)新的攔截器:

  • 攔截器4 :處理響應(yīng)后的 失敗重試和重定向功能

沒錯(cuò),剛才只考慮到請求成功,請求失敗了要不要重試呢?響應(yīng)碼為301、302時(shí)候的重定向處理?這都屬于要重新請求的部分,肯定不能丟給用戶,需要網(wǎng)絡(luò)框架自己給處理好。就叫它 重試和重定向攔截器吧。

  • 攔截器5 :處理響應(yīng)前的 緩存復(fù)用 ,處理響應(yīng)后的 緩存響應(yīng)數(shù)據(jù) 。

還有一個(gè)網(wǎng)絡(luò)請求有可能的需求就是關(guān)于緩存,這個(gè)緩存的概念可能有些朋友了解的不多,其實(shí)它多用于瀏覽器中。

瀏覽器緩存一般分為兩部分: 強(qiáng)制緩存和協(xié)商緩存 。

強(qiáng)制緩存 就是服務(wù)器會(huì)告訴客戶端該怎么緩存,例如 cache-Control 字段,隨便舉幾個(gè)例子:

private
max-age=xxx
no-cache
no-store

協(xié)商緩存 就是需要客戶端和服務(wù)器進(jìn)行協(xié)商后再?zèng)Q定是否使用緩存,比如強(qiáng)制緩存過期失效了,就要再次請求服務(wù)器,并帶上緩存標(biāo)志,例如Etag。

客戶端再次進(jìn)行請求的時(shí)候,請求頭帶上 If-None-Match ,也就是之前服務(wù)器返回的Etag值。

Etag值就是文件的唯一標(biāo)示,服務(wù)器通過某個(gè)算法對資源進(jìn)行計(jì)算,取得一串值(類似于文件的md5值),之后將該值通過etag返回給客戶端

然后服務(wù)器就會(huì)將 Etag 值和服務(wù)器本身文件的 Etag 值進(jìn)行比較,如果一樣則數(shù)據(jù)沒改變,就返回 304 ,代表你要請求的數(shù)據(jù)沒改變,你直接用就行啦。

如果不一致,就返回新的數(shù)據(jù),這時(shí)候的響應(yīng)碼就是正常的 200 。

這個(gè)攔截器就是用于處理這些情況,我們就叫它 緩存攔截器 吧。

  • 攔截器6: 自定義攔截器

最后就是自定義的攔截器了,要給開發(fā)者一個(gè)可以自定義的攔截器,用于統(tǒng)一處理請求或響應(yīng)數(shù)據(jù)。

這下好像齊了,至于之前說的7個(gè)攔截器還有1個(gè),留個(gè)懸念最后再說。

最后再給他們排個(gè)序吧:

  • 1、自定義攔截器的公共參數(shù)處理。
  • 2、封裝攔截器封裝請求報(bào)文
  • 3、緩存攔截器的緩存復(fù)用。
  • 4、連接攔截器建立TCP連接。
  • 5、IO攔截器的數(shù)據(jù)寫入。
  • 6、IO攔截器的數(shù)據(jù)讀取。
  • 7、緩存攔截器保存響應(yīng)數(shù)據(jù)緩存。
  • 8、封裝攔截器分析響應(yīng)報(bào)文
  • 9、重試和重定向攔截器處理重試和重定向情況。
  • 10、自定義攔截器統(tǒng)一處理響應(yīng)數(shù)據(jù)。

有點(diǎn)繞,來張圖瞧一瞧:

從網(wǎng)絡(luò)請求過程看OkHttp攔截器

 

所以,攔截器的順序也基本固定了:

  • 1、自定義攔截器
  • 2、重試和重定向攔截器
  • 3、封裝攔截器
  • 4、緩存攔截器
  • 5、連接攔截器
  • 6、IO攔截器

下面具體看看吧。

自定義攔截器

在請求之前,我們一般創(chuàng)建自己的自定義攔截器,用于添加一些接口公共參數(shù),比如把 token 加到Header中。

class MyInterceptor() : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()

        request = request.newBuilder()
                    .addHeader("token", "token")
                    .url(url)
                    .build()

        return chain.proceed(request)
    }

要注意的是,別忘了調(diào)用 chain.proceed ,否則這條鏈就無法繼續(xù)下去了。

在獲取響應(yīng)之后,我們一般用攔截器進(jìn)行結(jié)果打印,比如常用的 HttpLoggingInterceptor 。

addInterceptor(
    HttpLoggingInterceptor().Apply {
        level = HttpLoggingInterceptor.Level.BODY
    }
)

重試和重定向攔截器(RetryAndFollowUpInterceptor)

為了方便理解,我對源碼進(jìn)行了修剪:scissors::

class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    while (true) {
      try {
        try {
          response = realChain.proceed(request)
        } catch (e: RouteException) {
          //路由錯(cuò)誤
          continue
        } catch (e: IOException) {
          // 請求錯(cuò)誤
          continue
        }

        //獲取響應(yīng)碼判斷是否需要重定向
        val followUp = followUpRequest(response, exchange)
        if (followUp == null) {
          //沒有重定向
          return response
        }
        //賦予重定向請求,再次進(jìn)入下一次循環(huán)
        request = followUp
      } 
    }
  }
}

這樣代碼就很清晰了,重試和重定向的處理都是需要重新請求,所以這里用到了while循環(huán)。

realChain.proceed
重定向
response

封裝攔截器(BridgeInterceptor)

class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //添加頭部信息
    requestBuilder.header("Content-Type", contentType.toString())
    requestBuilder.header("Host", userRequest.url.toHostHeader())
    requestBuilder.header("Connection", "Keep-Alive")
    requestBuilder.header("Accept-Encoding", "gzip")
    requestBuilder.header("Cookie", cookieHeader(cookies))
    requestBuilder.header("User-Agent", userAgent)

    val networkResponse = chain.proceed(requestBuilder.build())

    //解壓
    val responseBuilder = networkResponse.newBuilder()
        .request(userRequest)
    if (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
      val responseBody = networkResponse.body
      if (responseBody != null) {
        val gzipSource = GzipSource(responseBody.source())
        responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
      }
    }

    return responseBuilder.build()
  }

請求前的代碼很簡單,就是添加了一些必要的頭部信息,包括 Content-Type、Host、Cookie 等等,封裝成一個(gè)完整的請求報(bào)文,然后交給下一個(gè)攔截器。

而獲取響應(yīng)后的代碼就有點(diǎn)不是很明白了, gzip 是啥? GzipSource 又是什么類?

gzip壓縮是基于deflate中的算法進(jìn)行壓縮的,gzip會(huì)產(chǎn)生自己的數(shù)據(jù)格式,gzip壓縮對于所需要壓縮的文件,首先使用LZ77算法進(jìn)行壓縮,再對得到的結(jié)果進(jìn)行huffman編碼,根據(jù)實(shí)際情況判斷是要用動(dòng)態(tài)huffman編碼還是靜態(tài)huffman編碼,最后生成相應(yīng)的gz壓縮文件。

簡單的說, gzip 就是一種壓縮方式,可以將數(shù)據(jù)進(jìn)行壓縮,在添加頭部信息的時(shí)候就添加了這樣一個(gè)頭部:

requestBuilder.header("Accept-Encoding", "gzip")

這一句其實(shí)就是在告訴服務(wù)器,客戶端所能接受的文件的壓縮格式,這里設(shè)置了 gzip 之后,服務(wù)器看到了就能把響應(yīng)報(bào)文數(shù)據(jù)進(jìn)行 gzip 壓縮再傳輸,提高傳輸效率,節(jié)省流量。

所以請求之后的這段關(guān)于 gzip 的處理其實(shí)就是客戶端對壓縮數(shù)據(jù)進(jìn)行解壓縮,而 GzipSource 是okio庫里面一個(gè)進(jìn)行解壓縮讀取數(shù)據(jù)的類。

緩存攔截器(CacheInterceptor)

繼續(xù)看緩存攔截器— CacheInterceptor 。

class CacheInterceptor(internal val cache: Cache?) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //取緩存
    val cacheCandidate = cache?.get(chain.request())
    
    //緩存策略類
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    // 如果不允許使用網(wǎng)絡(luò),并且緩存數(shù)據(jù)為空
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)//504
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // 如果不允許使用網(wǎng)絡(luò),但是有緩存
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    
    networkResponse = chain.proceed(networkRequest)

    // 如果緩存不為空
    if (cacheResponse != null) {
      //304,表示數(shù)據(jù)未修改
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        cache.update(cacheResponse, response)
        return response
      } 
    }

    //如果開發(fā)者設(shè)置了緩存,則將響應(yīng)數(shù)據(jù)緩存
    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        //緩存header
        val cacheRequest = cache.put(response)
        //緩存body
        return cacheWritingResponse(cacheRequest, response)
      }
    }

    return response
  }
}

還是分兩部分看:

  • 請求之前 ,通過request獲取了緩存,然后判斷緩存為空,就直接返回code為504的結(jié)果。如果有緩存并且緩存可用,則直接返回緩存。
  • 請求之后 ,如果返回 304 代表服務(wù)器數(shù)據(jù)沒修改,則直接返回緩存。如果 cache 不為空,那么就把 response 緩存下來。

這樣看是不是和上面我們說過的緩存機(jī)制對應(yīng)上了?請求之前就是處理 強(qiáng)制緩存 的情況,請求之后就會(huì)處理 協(xié)商緩存 的情況。

但是還是有幾個(gè)問題需要弄懂:

1、緩存是怎么存儲(chǔ)和獲取的?

2、每次請求都會(huì)去存儲(chǔ)和獲取緩存嗎?

3、緩存策略(CacheStrategy)到底是怎么處理網(wǎng)絡(luò)和緩存的?networkRequest什么時(shí)候?yàn)榭眨?/p>

首先,看看緩存哪里取的:

val cacheCandidate = cache?.get(chain.request())

internal fun get(request: Request): Response? {
    val key = key(request.url)
    val snapshot: DiskLruCache.Snapshot = try {
      cache[key] ?: return null
    } 

    val entry: Entry = try {
      Entry(snapshot.getSource(ENTRY_METADATA))
    } 

    val response = entry.response(snapshot)
    if (!entry.matches(request, response)) {
      response.body?.closeQuietly()
      return null
    }

    return response
  }

通過 cache.get 方法獲取了response緩存,get方法中主要是用到了請求 Request的url 來作為獲取緩存的標(biāo)志。

所以我們可以推斷,緩存的獲取是通過請求的url作為key來獲取的。

那么 cache 又是哪里來的呢?

val cache: Cache? = builder.cache

interceptors += CacheInterceptor(client.cache)

class CacheInterceptor(internal val cache: Cache?) : Interceptor

沒錯(cuò),就是實(shí)例化 CacheInterceptor 的時(shí)候傳進(jìn)去的,所以這個(gè)cache是需要我們創(chuàng)建 OkHttpClient 的時(shí)候設(shè)置的,比如這樣:

val okHttpClient =
      OkHttpClient().newBuilder()
          .cache(Cache(cacheDir, 10 * 1024 * 1024))
          .build()

這樣設(shè)置之后, okhttp 就知道 cache 存在哪里,大小為多少,然后就可以進(jìn)行服務(wù)器響應(yīng)的緩存處理了。

所以第二個(gè)問題也解決了,并不是每次請求都會(huì)去處理緩存,而是開發(fā)者需要去設(shè)置緩存的存儲(chǔ)目錄和大小,才會(huì)針對緩存進(jìn)行這一系列的處理操作。

最后再看看緩存策略方法 CacheStrategy.Factory().compute()

class CacheStrategy internal constructor(
  val networkRequest: Request?,
  val cacheResponse: Response?
)

    fun compute(): CacheStrategy {
      val candidate = computeCandidate()
      return candidate
    }


    private fun computeCandidate(): CacheStrategy {
      //沒有緩存情況下,返回空緩存
      if (cacheResponse == null) {
        return CacheStrategy(request, null)
      }
      //...

      //緩存控制不是 no-cache,且未過期
      if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        val builder = cacheResponse.newBuilder()
        return CacheStrategy(null, builder.build())
      }

      
      return CacheStrategy(conditionalRequest, cacheResponse)
    }

在這個(gè)緩存策略生存的過程中,只有一種情況下會(huì)返回緩存,也就是緩存控制不是 no-cache ,并且緩存沒過期情況下,就返回緩存,然后設(shè)置networkRequest為空。

所以也就對應(yīng)上一開始緩存攔截器中的獲取緩存后的判斷:

// 如果不允許使用網(wǎng)絡(luò),但是有緩存,則直接返回緩存
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

連接攔截器(ConnectInterceptor)

繼續(xù),連接攔截器,之前說了是關(guān)于 TCP連接 的。

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }
}

代碼看著倒是挺少的,但其實(shí)這里面很復(fù)雜很復(fù)雜,不著急,我們慢慢說。

這段代碼就執(zhí)行了一個(gè)方法就是 initExchange 方法:

internal fun initExchange(chain: RealInterceptorChain): Exchange {
    val codec = exchangeFinder.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    return result
  }

  fun find(
    client: OkHttpClient,
    chain: RealInterceptorChain
  ): ExchangeCodec {
    try {
      val resultConnection = findHealthyConnection(
          connectTimeout = chain.connectTimeoutMillis,
          readTimeout = chain.readTimeoutMillis,
          writeTimeout = chain.writeTimeoutMillis,
          pingIntervalMillis = client.pingIntervalMillis,
          connectionRetryEnabled = client.retryOnConnectionFailure,
          doExtensiveHealthChecks = chain.request.method != "GET"
      )
      return resultConnection.newCodec(client, chain)
    } 
  }

好像有一點(diǎn)眉目了,找到一個(gè)ExchangeCodec類,并封裝成一個(gè)Exchange類。

ExchangeCodec
Exchange

明白了,這個(gè)連接攔截器(ConnectInterceptor)就是找到一個(gè)可用連接唄,也就是TCP連接,這個(gè)連接就是用于HTTP請求和響應(yīng)的。

你可以把它可以理解為一個(gè) 管道 ,有了這個(gè)管道,才能把數(shù)據(jù)丟進(jìn)去,也才可以從管道里面取數(shù)據(jù)。

而這個(gè) ExchangeCodec ,編碼解碼器就是用來讀取和輸送到這個(gè)管道的一個(gè)工具,相當(dāng)于把你的數(shù)據(jù)封裝成這個(gè)連接(管道)需要的格式。

我咋知道的?我貼一段ExchangeCodec代碼你就明白了:

//Http1ExchangeCodec.JAVA
  fun writeRequest(headers: Headers, requestLine: String) {
    check(state == STATE_IDLE) { "state: $state" }
    sink.writeUtf8(requestLine).writeUtf8("rn")
    for (i in 0 until headers.size) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("rn")
    }
    sink.writeUtf8("rn")
    state = STATE_OPEN_REQUEST_BODY
  }

這里貼的是 Http1ExchangeCodec 的write代碼,也就是Http1的編碼解碼器。

很明顯,就是將Header信息一行一行寫到sink中,然后再由sink交給輸出流,具體就不分析了。只要知道這個(gè)編碼解碼器就是用來處理連接中進(jìn)行輸送的數(shù)據(jù)即可。

然后就是這個(gè)攔截器的關(guān)鍵了,連接到底是怎么獲取的呢?繼續(xù)看看:

private fun findConnection(): RealConnection {

    // 1、復(fù)用當(dāng)前連接
    val callConnection = call.connection 
    if (callConnection != null) {
        //檢查這個(gè)連接是否可用和可復(fù)用
        if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
          toClose = call.releaseConnectionNoEvents()
        }
      return callConnection
    }

   //2、從連接池中獲取可用連接
    if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
      val result = call.connection!!
      eventListener.connectionAcquired(call, result)
      return result
    }

    //3、從連接池中獲取可用連接(通過一組路由routes)
    if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
        val result = call.connection!!
        return result
      }
    route = localRouteSelection.next()


    // 4、創(chuàng)建新連接
    val newConnection = RealConnection(connectionPool, route)
    newConnection.connect

    // 5、再獲取一次連接,防止在新建連接過程中有其他競爭連接被創(chuàng)建了
    if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) { 
      return result
    }

    //6、還是要使用創(chuàng)建的新連接,放入連接池,并返回
    connectionPool.put(newConnection)
    return newConnection
  }

獲取連接的過程很復(fù)雜,為了方便看懂,我簡化了代碼,分成了6步。

  • 1、檢查當(dāng)前連接是否可用。

怎么判斷可用的?主要做了兩個(gè)判斷

1)判斷是否不再接受新的連接

2)判斷和當(dāng)前請求有相同的主機(jī)名和端口號。

這倒是很好理解,要這個(gè)連接是連接的同一個(gè)地方才能復(fù)用是吧,同一個(gè)地方怎么判斷?就是判斷 主機(jī)名和端口號 。

還有個(gè)問題就是為什么有當(dāng)前連接??明明還沒開始連接也沒有獲取連接啊,怎么連接就被賦值了?

還記得 重試和重定向 攔截器嗎?對了,就是當(dāng)請求失敗需要重試的時(shí)候或者重定向的時(shí)候,這時(shí)候連接還在呢,是可以直接進(jìn)行復(fù)用的。

  • 2和3、從連接池中獲取可用連接

第2步和第3步都是從連接池獲取連接,有什么不一樣嗎?

connectionPool.callAcquirePooledConnection(address, call, null, false)
connectionPool.callAcquirePooledConnection(address, call, routes, false)

好像多了一個(gè) routes 字段?

這里涉及到HTTP/2的一個(gè)技術(shù),叫做 HTTP/2 CONNECTION COALESCING (連接合并),什么意思呢?

假設(shè)有兩個(gè)域名,可以解析為相同的IP地址,并且是可以用相同的TLS證書(比如通配符證書),那么客戶端可以重用相同的 TCP連接 從這兩個(gè)域名中獲取資源。

再看回我們的連接池,這個(gè) routes 就是當(dāng)前域名(主機(jī)名)可以被解析的 ip地址 集合,這兩個(gè)方法的區(qū)別也就是一個(gè)傳了路由地址,一個(gè)沒有傳。

繼續(xù)看
callAcquirePooledConnection 代碼:

internal fun isEligible(address: Address, routes: List<Route>?): Boolean {

    if (address.url.host == this.route().address.url.host) {
      return true 
    }

    //HTTP/2 CONNECTION COALESCING
    if (http2Connection == null) return false
    if (routes == null || !routeMatchesAny(routes)) return false
    if (address.hostnameVerifier !== OkHostnameVerifier) return false
    return true 
  }

1)判斷主機(jī)名、端口號等,如果請求完全相同就直接返回這個(gè)連接。

2)如果主機(jī)名不同,還可以判斷是不是 HTTP/2 請求,如果是就繼續(xù)判斷路由地址,證書,如果都能匹配上,那么這個(gè)連接也是可用的。

  • 4、創(chuàng)建新連接

如果沒有從連接池中獲取到新連接,那么就創(chuàng)建一個(gè)新連接,這里就不多說了,其實(shí)就是調(diào)用到 socket.connect 進(jìn)行TCP連接。

  • 5、再從連接池獲取一次連接,防止在新建連接過程中有其他競爭連接被創(chuàng)建了

創(chuàng)建了新連接,為什么還要去連接池獲取一次連接呢?

因?yàn)樵谶@個(gè)過程中,有可能有其他的請求和你一起創(chuàng)建了新連接,所以我們需要再去取一次連接,如果有可以用的,就直接用它,防止資源浪費(fèi)。

其實(shí)這里又涉及到HTTP2的一個(gè)知識點(diǎn): 多路復(fù)用 。

簡單的說,就是不需要當(dāng)前連接的上一個(gè)請求結(jié)束之后再去進(jìn)行下一次請求,只要有連接就可以直接用。

HTTP/2引入二進(jìn)制數(shù)據(jù)幀和流的概念,其中幀對數(shù)據(jù)進(jìn)行順序標(biāo)識,這樣在收到數(shù)據(jù)之后,就可以按照序列對數(shù)據(jù)進(jìn)行合并,而不會(huì)出現(xiàn)合并后數(shù)據(jù)錯(cuò)亂的情況。同樣是因?yàn)橛辛诵蛄校?wù)器就可以并行的傳輸數(shù)據(jù),這就是流所做的事情。

所以在 HTTP/2 中可以保證在同一個(gè)域名只建立一路連接,并且可以并發(fā)進(jìn)行請求。

  • 6、新連接放入連接池,并返回

最后一步好理解吧,走到這里說明就要用這個(gè)新連接了,那么就把它存到連接池,返回這個(gè)連接。

這個(gè)攔截器確實(shí)麻煩,大家好好梳理下吧,我也再來個(gè)圖:

從網(wǎng)絡(luò)請求過程看OkHttp攔截器

 

IO攔截器(CallServerInterceptor)

連接拿到了,編碼解碼器有了,剩下的就是發(fā)數(shù)據(jù),讀數(shù)據(jù)了,也就是跟 I/O 相關(guān)的工作。

class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {

  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    
    //寫header數(shù)據(jù)
    exchange.writeRequestHeaders(request)
    //寫body數(shù)據(jù)
    if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
      requestBody.writeTo(bufferedRequestBody)
    } else {
      exchange.noRequestBody()
    }

    //結(jié)束請求
    if (requestBody == null || !requestBody.isDuplex()) {
      exchange.finishRequest()
    }
    
    //獲取響應(yīng)數(shù)據(jù)
    var response = responseBuilder
        .request(request)
        .handshake(exchange.connection.handshake())
        .build()

    var code = response.code
    response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build()
    return response
  }
}

這個(gè)攔截器 倒是沒干什么活,之前的攔截器兄弟們都把準(zhǔn)備工作干完了,它就調(diào)用下 exchange 類的各種方法,寫入 header,body ,拿到 code,response 。

這活可干的真輕松啊。

被遺漏的自定義攔截器(networkInterceptors)

好了,最后補(bǔ)上這個(gè)攔截器 networkInterceptors ,它也是一個(gè)自定義攔截器,位于 CallServerInterceptor 之前,屬于倒數(shù)第二個(gè)攔截器。

那為什么 OkHttp 在有了一個(gè)自定義攔截器的前提下又提供了一個(gè)攔截器呢?

可以發(fā)現(xiàn),這個(gè)攔截器的位置是比較深的位置,處在發(fā)送數(shù)據(jù)的前一刻,以及收到數(shù)據(jù)的第一刻。

這么敏感的位置,決定了通過這個(gè)攔截器可以看到更多的信息,比如:

請求之前
請求之后

所以,這個(gè)攔截器就是用來 網(wǎng)絡(luò)調(diào)試 的,調(diào)試比較底層、更全面的數(shù)據(jù)。

總結(jié)

最后再回顧下每個(gè)攔截器的作用:

addInterceptor(Interceptor)
RetryAndFollowUpInterceptor
BridgeInterceptor
CacheInterceptor
ConnectInterceptor
networkInterceptors
CallServerInterceptor

參考

https://www.jianshu.com/p/bfb13eb3a425

https://segmentfault.com/a/1190000020386580

https://www.jianshu.com/p/02db8b55aae9

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc


作者: jimuzz

來源
:https://www.cnblogs.com/jimuzz/p/14557234.html

分享到:
標(biāo)簽:攔截器 OkHttp
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定