Okhttp同步與異步二

之前根據(jù)Okhttp使用流程,逐塊看了源碼內(nèi)的相關(guān)內(nèi)容介紹。現(xiàn)在去看同步與異步之間的差異

   val mOk = OkHttpClient()
        val request = Request.Builder()
            .url("請求地址")
            .get()//請求方式
            .build()
        val call = mOk.newCall(request)

同步分析:

call.execute()

call.execute()開啟同步請求,返回Response,點(diǎn)進(jìn)去看看

  override fun execute(): Response {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    timeout.enter()
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }
  • timeout.enter()衩辟,內(nèi)部代碼如下:如果沒有超時(shí)也沒有結(jié)束? 不允許進(jìn)入,阻塞
  fun enter() {
    val timeoutNanos = timeoutNanos()
    val hasDeadline = hasDeadline()
    if (timeoutNanos == 0L && !hasDeadline) {
      return
    }
    scheduleTimeout(this, timeoutNanos, hasDeadline)
  }
  • 事件偵聽器EventListener回調(diào)callStart()
  private fun callStart() {
    this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
    eventListener.callStart(this)
  }
 /** Used by [Call.execute] to signal it is in-flight. */
 @Synchronized internal fun executed(call: RealCall) {
   runningSyncCalls.add(call)
 }
  • 構(gòu)建一個(gè)攔截器鏈getResponseWithInterceptorChain() 返回Response
 internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors //自定義
    interceptors += RetryAndFollowUpInterceptor(client) //錯(cuò)誤和重定向
    interceptors += BridgeInterceptor(client.cookieJar) //橋梁:應(yīng)用程序過渡到網(wǎng)絡(luò)
    interceptors += CacheInterceptor(client.cache) //緩存
    interceptors += ConnectInterceptor //連接
    if (!forWebSocket) {
      interceptors += client.networkInterceptors //網(wǎng)絡(luò)
    }
    interceptors += CallServerInterceptor(forWebSocket) //對服務(wù)器進(jìn)行網(wǎng)絡(luò)調(diào)用

    //承載整個(gè)攔截器鏈的具體攔截器鏈
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )

    //返回response
    var calledNoMoreExchanges = false
    try {
      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)
      }
    }
  }

異步分析:

     call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
                Log.e(TAG, "請求成功")
            }

            override fun onFailure(call: Call, e: IOException) {
                Log.e(TAG, "請求失敗")
            }
        })

異步執(zhí)行是通過call.enqueue(responseCallback: Callback)來執(zhí)行艺晴,點(diǎn)進(jìn)去查看

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }
  • 事件偵聽器EventListener回調(diào)callStart()和同步請求一樣
  • 調(diào)用client.dispatcher.enqueue(AsyncCall(responseCallback))并傳入了一個(gè)實(shí)例AsyncCall
  inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
    @Volatile var callsPerHost = AtomicInteger(0)
      private set

    fun reuseCallsPerHostFrom(other: AsyncCall) {
      this.callsPerHost = other.callsPerHost
    }

    val host: String
      get() = originalRequest.url.host

    val request: Request
        get() = originalRequest

    val call: RealCall
        get() = this@RealCall

    /**
     * Attempt to enqueue this async call on [executorService]. This will attempt to clean up
     * if the executor has been shut down by reporting the call as failed.
     */
    fun executeOn(executorService: ExecutorService) {
      client.dispatcher.assertThreadDoesntHoldLock()

      var success = false
      try {
        executorService.execute(this)
        success = true
      } catch (e: RejectedExecutionException) {
        val ioException = InterruptedIOException("executor rejected")
        ioException.initCause(e)
        noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }

    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            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)
        }
      }
    }
  }

AsyncCall繼承了Runnable ,所以具體的請求流程都在run()里面進(jìn)行處理,和同步請求流程一樣調(diào)用timeout.enter() 最后也會構(gòu)建一個(gè)攔截鏈getResponseWithInterceptorChain() 返回Response,成功回調(diào) fun onResponse(call: Call, response: Response),失敗回調(diào)fun onFailure(call: Call, e: IOException)叶雹〔萍ⅲ回過頭來繼續(xù)看client.dispatcher.enqueue

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

      // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
      // the same host.
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

這里將AsyncCall加入到了準(zhǔn)備執(zhí)行的隊(duì)列(readyAsyncCalls.add(call))中,往下看if里面的邏輯折晦,首先是findExistingCallWithHost(host: String)方法

  private fun findExistingCallWithHost(host: String): AsyncCall? {
    for (existingCall in runningAsyncCalls) {
      if (existingCall.host == host) return existingCall
    }
    for (existingCall in readyAsyncCalls) {
      if (existingCall.host == host) return existingCall
    }
    return null
  }

在這個(gè)方法里面他主要在查找隊(duì)列中已經(jīng)存在的host并返回钥星,回調(diào)asyncCall.reuseCallsPerHostFrom使其共享對同一主機(jī)的現(xiàn)有運(yùn)行調(diào)用的AtomicInteger,再回到異步enqueue(call: AsyncCall)方法中满着,看最后一步調(diào)用promoteAndExecute()方法

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 // Max capacity.
        if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.

        i.remove()
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

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

    return isRunning
  }

再同步代碼塊內(nèi)對readyAsyncCalls隊(duì)列進(jìn)行迭代谦炒,將符合要求的條件從隊(duì)列中移除添加到runningAsyncCalls隊(duì)列中,不符合的話繼續(xù)待在readyAsyncCalls等待執(zhí)行风喇,最后調(diào)用asyncCall.executeOn(executorService)放入到線程中執(zhí)行

總結(jié):

  1. 同步請求:發(fā)送一個(gè)請求后需要等待返回宁改,才能發(fā)送下一個(gè)請求。
  2. 異步請求:發(fā)送一個(gè)請求后不需要等待返回魂莫,可以繼續(xù)發(fā)送还蹲,因?yàn)閮?nèi)部有兩個(gè)隊(duì)列,等待執(zhí)行(readyAsyncCalls)和執(zhí)行中(runningAsyncCalls),加入了AtomicInteger和線程池支持高并發(fā)
  3. Dispatcher:同步谜喊、異步都由Dispatcher進(jìn)行統(tǒng)一管理
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末潭兽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子斗遏,更是在濱河造成了極大的恐慌山卦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诵次,死亡現(xiàn)場離奇詭異账蓉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逾一,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門铸本,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嬉荆,你說我怎么就攤上這事归敬】岷” “怎么了鄙早?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長椅亚。 經(jīng)常有香客問我限番,道長,這世上最難降的妖魔是什么呀舔? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任弥虐,我火速辦了婚禮,結(jié)果婚禮上媚赖,老公的妹妹穿的比我還像新娘霜瘪。我一直安慰自己,他們只是感情好惧磺,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布颖对。 她就那樣靜靜地躺著,像睡著了一般磨隘。 火紅的嫁衣襯著肌膚如雪缤底。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天番捂,我揣著相機(jī)與錄音个唧,去河邊找鬼。 笑死设预,一個(gè)胖子當(dāng)著我的面吹牛徙歼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼魄梯,長吁一口氣:“原來是場噩夢啊……” “哼呼股!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起画恰,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤彭谁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后允扇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缠局,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年考润,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狭园。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡糊治,死狀恐怖唱矛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情井辜,我是刑警寧澤绎谦,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站粥脚,受9級特大地震影響窃肠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刷允,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一冤留、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧树灶,春花似錦纤怒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至土砂,卻和暖如春州既,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萝映。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工吴叶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人序臂。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓蚌卤,卻偏偏與公主長得像实束,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子逊彭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

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