OkHttp源碼-流程-攔截器分析

前言

網(wǎng)絡(luò)模塊在開發(fā)中很常見了吧,也可以說是很重要了吧动雹,所以梳理梳理槽卫,做做總結(jié) 、產(chǎn)出以及記錄胰蝠。
總的來說歼培,本文基于OkHttp4.5.0震蒋,Kotlin版本,做了對OkHttp的基本請求、整體流程、同/異步請求胡控、責(zé)任鏈調(diào)度以及CacheInterceptor的分析。

Http

網(wǎng)絡(luò)的底層蕾各,萬變不離其底層,這部分還是很重要的,以下兩篇都寫得非常nice
面試帶你飛:這是一份全面的 計算機網(wǎng)絡(luò)基礎(chǔ) 總結(jié)攻略
TCP協(xié)議靈魂之問,鞏固你的網(wǎng)路底層基礎(chǔ)

OkHttp

How 基本請求

fun load() {
      //1.創(chuàng)建請求(包含url,method,headers,body)
      val request = Request
              .Builder()
              .url("")
              .build()
       //2.創(chuàng)建OkHttpClient (包含分發(fā)器直砂、攔截器、DNS等)
       val okHttpClient = OkHttpClient.Builder().build()
       //3.創(chuàng)建Call(用于調(diào)用請求)
       val newCall = okHttpClient.newCall(request)
       //4.通過異步請求數(shù)據(jù)
       newCall.enqueue(object :Callback{
           override fun onFailure(call: Call, e: IOException) {}
           override fun onResponse(call: Call, response: Response) {}
       })
       //4.通過同步請求數(shù)據(jù)
       val response =  newCall.execute()
}
  • 流程圖
okhttp流程圖.png

前面就是基于構(gòu)造者的創(chuàng)建(主要是量太大,不想占篇幅所以就不展示出來了)
關(guān)鍵(并不)處在于RealCall的請求

  • 1.異步請求 RealCall#enqueue(responseCallback: Callback)
//RealCall#enqueue(responseCallback: Callback)  
  override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      //檢查這個call是否已經(jīng)執(zhí)行過了丐枉,每 call只能被執(zhí)行一次
      check(!executed) { "Already Executed" }
      executed = true
    }
    //Call被調(diào)用時調(diào)用哆键,調(diào)用了EventListener#callStart()掘托、擴展此類可以監(jiān)視應(yīng)用程序的HTTP調(diào)用的數(shù)量瘦锹,大小和持續(xù)時間
    callStart()
    //1.這里創(chuàng)建了AsyncCall實際是個Runnable后面再提(標(biāo)記為3)
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

//1.Dispatcher#enqueue(call: AsyncCall)
  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      //添加到異步調(diào)用的隊列
      readyAsyncCalls.add(call)

      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    //2
    promoteAndExecute()
  }

//2.Dispatcher#promoteAndExecute()   
//主要是將[readyAsyncCalls]過渡到[runningAsyncCalls]
 private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      while (i.hasNext()) {
        val asyncCall = i.next()

        if (runningAsyncCalls.size >= this.maxRequests) break // 最大容量. 64
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // 最大主機容量. 5

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        //異步請求各自添加到異步調(diào)用隊列和集合中
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      //同步隊列和異步隊列中超過一個 代表正在運行
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      //3.
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

//3.RealCall.kt中的內(nèi)部類
  internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
    fun executeOn(executorService: ExecutorService) {
      ...
        //有點長 直接說重點了
        //放到線程池中 執(zhí)行這個Runnable
        executorService.execute(this)
      ...
  
    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        ...
        try {
          //兜兜轉(zhuǎn)轉(zhuǎn) 終于調(diào)用這個關(guān)鍵方法了
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          //接口回調(diào)數(shù)據(jù)
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
          } else {
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
          //移除隊列
          client.dispatcher.finished(this)
        }
      }
    }
  }
  • 2.同步請求 RealCall#execute()
  override fun execute(): Response {
    //同樣判斷是否執(zhí)行過
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    timeout.enter()
    //同樣監(jiān)聽
    callStart()
    try {
      //同樣執(zhí)行
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      //同樣移除
      client.dispatcher.finished(this)
    }
  }

(Real)關(guān)鍵處的關(guān)鍵在于getResponseWithInterceptorChain()方法

  @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // 構(gòu)建完整的攔截器堆棧
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors                    //用戶自己攔截器
    interceptors += RetryAndFollowUpInterceptor(client)    //失敗后的重試和重定向
    interceptors += BridgeInterceptor(client.cookieJar)    //橋接用戶的信息和服務(wù)器的信息
    interceptors += CacheInterceptor(client.cache)         //處理緩存相關(guān)
    interceptors += ConnectInterceptor                     //負責(zé)與服務(wù)器連接
    if (!forWebSocket) {
      interceptors += client.networkInterceptors           //配置 OkHttpClient 時設(shè)置的 
    }
    interceptors += CallServerInterceptor(forWebSocket)    //負責(zé)向服務(wù)器發(fā)送請求數(shù)據(jù)、從服務(wù)器讀取響應(yīng)數(shù)據(jù)
    //創(chuàng)建攔截鏈
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )

    var calledNoMoreExchanges = false
    try {
      //攔截鏈的執(zhí)行  1.
      val response = chain.proceed(originalRequest)
      if (isCanceled()) {
        response.closeQuietly()
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        noMoreExchanges(null)
      }
    }
  }

//1.RealInterceptorChain#proceed(request: Request)
@Throws(IOException::class)
  override fun proceed(request: Request): Response {
    ...
    // copy出新的攔截鏈闪盔,鏈中的攔截器集合index+1
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    //調(diào)用攔截器的intercept(chain: Chain): Response 返回處理后的數(shù)據(jù) 交由下一個攔截器處理
    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")
    ...
    //返回最終的響應(yīng)體
    return response
  }

這里采用責(zé)任鏈的模式來使每個功能分開弯院,每個Interceptor自行完成自己的任務(wù),并且將不屬于自己的任務(wù)交給下一個泪掀,簡化了各自的責(zé)任和邏輯听绳,和View中的事件分發(fā)機制類似.

  • 再來一張時序圖
okhttp 時序圖.jpg

OkHttp關(guān)鍵中的核心就是這每一個攔截器 具體怎么實現(xiàn)還需要look look,這里就看一個CacheInterceptor吧

  • CacheInterceptor.kt
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //在Cache(DiskLruCache)類中 通過request.url匹配response
    val cacheCandidate = cache?.get(chain.request())
    //記錄當(dāng)前時間點
    val now = System.currentTimeMillis()
    //緩存策略 有兩種類型 
    //networkRequest 網(wǎng)絡(luò)請求
    //cacheResponse 緩存的響應(yīng)
    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse
    //計算請求次數(shù)和緩存次數(shù)
    cache?.trackResponse(strategy)
    ...
    // 如果 禁止使用網(wǎng)絡(luò) 并且 緩存不足异赫,返回504和空body的Response
    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)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build()
    }

    // 如果策略中不能使用網(wǎng)絡(luò)椅挣,就把緩存中的response封裝返回
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build()
    }
    //調(diào)用攔截器process從網(wǎng)絡(luò)獲取數(shù)據(jù)
    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()
      }
    }
  
    //如果有緩存的Response
    if (cacheResponse != null) {
      //如果網(wǎng)絡(luò)請求返回code為304 即說明資源未修改
      if (networkResponse?.code == HTTP_NOT_MODIFIED) {
        //直接封裝封裝緩存的Response返回即可
        val 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()

        // 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 {
        cacheResponse.body?.closeQuietly()
      }
    }

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

    if (cache != null) {
      //判斷是否具有主體 并且 是否可以緩存供后續(xù)使用
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // 加入緩存中
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response)
      }
      //如果請求方法無效 就從緩存中remove掉
      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }
    
    return response
  }

總結(jié)

  • OkHttpClient為Request創(chuàng)建RealCall
  • RealCall的enqueue(responseCallback: Callback)異步請求通過Dispatcher實現(xiàn),和execute()同步請求一樣最終調(diào)用getResponseWithInterceptorChain()
  • getResponseWithInterceptorChain()通過責(zé)任鏈模式用每個不同職責(zé)Interceptor處理Response返回數(shù)據(jù)

最后

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末塔拳,一起剝皮案震驚了整個濱河市鼠证,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌靠抑,老刑警劉巖量九,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颂碧,居然都是意外死亡荠列,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門载城,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肌似,“玉大人,你說我怎么就攤上這事诉瓦〈ǘ樱” “怎么了受楼?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呼寸。 經(jīng)常有香客問我艳汽,道長,這世上最難降的妖魔是什么对雪? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任河狐,我火速辦了婚禮,結(jié)果婚禮上瑟捣,老公的妹妹穿的比我還像新娘馋艺。我一直安慰自己,他們只是感情好迈套,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布捐祠。 她就那樣靜靜地躺著,像睡著了一般桑李。 火紅的嫁衣襯著肌膚如雪踱蛀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天贵白,我揣著相機與錄音率拒,去河邊找鬼。 笑死禁荒,一個胖子當(dāng)著我的面吹牛猬膨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呛伴,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼勃痴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了热康?” 一聲冷哼從身側(cè)響起沛申,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褐隆,沒想到半個月后污它,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡庶弃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年衫贬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歇攻。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡固惯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缴守,到底是詐尸還是另有隱情葬毫,我是刑警寧澤镇辉,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站贴捡,受9級特大地震影響忽肛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烂斋,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一屹逛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧汛骂,春花似錦罕模、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝶念,卻和暖如春抛腕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祸轮。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工兽埃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人适袜。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像舷夺,于是被迫代替她去往敵國和親苦酱。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • OkHttp源碼的samples的簡單使用的示例: public static void main(String....
    _warren閱讀 749評論 0 1
  • 那么我今天給大家簡單地講一下Okhttp這款網(wǎng)絡(luò)框架及其原理给猾。它是如何請求數(shù)據(jù)疫萤,如何響應(yīng)數(shù)據(jù)的 有什么優(yōu)點?它的應(yīng)...
    卓而不群_0137閱讀 315評論 0 1
  • 這篇文章主要講 Android 網(wǎng)絡(luò)請求時所使用到的各個請求庫的關(guān)系敢伸,以及 OkHttp3 的介紹扯饶。(如理解有誤,...
    小莊bb閱讀 1,159評論 0 4
  • 簡介 OkHttp 是一款用于 Android 和 Java 的網(wǎng)絡(luò)請求庫池颈,也是目前 Android 中最火的一個...
    然則閱讀 1,203評論 1 39
  • OkHttp源碼分析 在現(xiàn)在的Android開發(fā)中尾序,請求網(wǎng)絡(luò)獲取數(shù)據(jù)基本上成了我們的標(biāo)配。在早期的Android開...
    BlackFlag閱讀 326評論 0 5