okhttp——網(wǎng)絡(luò)請(qǐng)求模型

簡(jiǎn)介

okhttp是Android中應(yīng)用最廣的http網(wǎng)絡(luò)請(qǐng)求框架。結(jié)構(gòu)優(yōu)雅煞茫,性能強(qiáng)大膜眠。我們通過(guò)閱讀它,對(duì)網(wǎng)絡(luò)庫(kù)的架構(gòu)進(jìn)行學(xué)習(xí)溜嗜。本篇主要閱讀okhttp的網(wǎng)絡(luò)請(qǐng)求攔截鏈模型。

基本結(jié)構(gòu)

okhttp采用拉截鏈的模型架谎,將網(wǎng)絡(luò)請(qǐng)求的各個(gè)部分炸宵,以一個(gè)個(gè)攔截器的方法,加入攔截鏈谷扣。


攔截鏈

詳細(xì)代碼

我們知道土全,在okhttp的任務(wù)調(diào)度模型中捎琐,最終任務(wù),會(huì)調(diào)用execute方法裹匙。我們先來(lái)看execute方法瑞凑。

    override fun execute() {
      var signalledCallback = false
      transmitter.timeoutEnter()
      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(INFO, "Callback failure for ${toLoggableString()}", e)
        } else {
          responseCallback.onFailure(this@RealCall, e)
        }
      } finally {
        client.dispatcher().finished(this)
      }
    }

這個(gè)方法中,實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求的關(guān)鍵調(diào)用是:getResponseWithInterceptorChain概页。其他主要還是調(diào)用回調(diào)或處理異常籽御。

拼裝攔截鏈

  @Throws(IOException::class)
  fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = ArrayList<Interceptor>()
    interceptors.addAll(client.interceptors())
    interceptors.add(RetryAndFollowUpInterceptor(client))
    interceptors.add(BridgeInterceptor(client.cookieJar()))
    interceptors.add(CacheInterceptor(client.internalCache()))
    interceptors.add(ConnectInterceptor(client))
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors())
    }
    interceptors.add(CallServerInterceptor(forWebSocket))

    val chain = RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis())

    var calledNoMoreExchanges = false
    try {
      val response = chain.proceed(originalRequest)
      if (transmitter.isCanceled) {
        closeQuietly(response)
        throw IOException("Canceled")
      }
      return response
    } catch (e: IOException) {
      calledNoMoreExchanges = true
      throw transmitter.noMoreExchanges(e) as Throwable
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null)
      }
    }
  }

這塊兒代碼基本還是簡(jiǎn)單清晰的。先用ArrayList<Interceptor>保存攔截器的隊(duì)列惰匙,然后生成RealInterceptorChain技掏,最后調(diào)用proceed方法,獲取response项鬼。

我們先來(lái)看攔截器的抽象實(shí)現(xiàn)哑梳。

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
interface Interceptor {
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response

  companion object {
    // This lambda conversion is for Kotlin callers expecting a Java SAM (single-abstract-method).
    @JvmName("-deprecated_Interceptor")
    inline operator fun invoke(
      crossinline block: (chain: Chain) -> Response
    ): Interceptor = object : Interceptor {
      override fun intercept(chain: Chain) = block(chain)
    }
  }

  interface Chain {
    fun request(): Request

    @Throws(IOException::class)
    fun proceed(request: Request): Response

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    fun connection(): Connection?

    fun call(): Call

    fun connectTimeoutMillis(): Int

    fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain

    fun readTimeoutMillis(): Int

    fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain

    fun writeTimeoutMillis(): Int

    fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
  }
}

intercept方法,就是攔截器的核心绘盟,輸入Chain鸠真,返回Response。

我們隨意找一個(gè)Interceptor來(lái)進(jìn)行閱讀

RetryAndFollowUpInterceptor

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      }
  ...
}

這一段邏輯中龄毡,我們傳入的是chain吠卷,即整個(gè)攔截鏈,會(huì)在中間調(diào)用realChain.proceed()稚虎。表示撤嫩,在當(dāng)前攔截器中,我們只做我們職責(zé)之類的邏輯蠢终,其余的邏輯序攘,交給傳入Chain的下一環(huán)

由此我們得知寻拂,RealInterceptorChain其實(shí)是一次請(qǐng)求所要做的所有工作程奠。每一個(gè)Interceptor只負(fù)責(zé)一部分工作。它們的順序是從外到內(nèi)祭钉,當(dāng)完成自己部分的外層功能后瞄沙,就會(huì)將接下來(lái)的工作,交給下一層去完成慌核。RetryAndFollowUpInterceptor只負(fù)責(zé)重試和重定向這些外層工作距境,其實(shí)邏輯會(huì)交由攔截器鏈的下一環(huán)節(jié)實(shí)現(xiàn)。Interceptor本身不用關(guān)心下一級(jí)的Interceptor是誰(shuí)垮卓。

接下來(lái)垫桂,我們?cè)倏匆幌拢琑ealInterceptorChain的邏輯粟按。

RealInterceptorChain

RealInterceptorChain中有一個(gè)index的索引霹粥。它標(biāo)識(shí)了當(dāng)前攔截器鏈路進(jìn)行到了哪一環(huán)。
我們著重看RealInterceptorChain的proceed方法疼鸟,看一下Interceptor是如何前進(jìn)到下一環(huán)的后控。

class RealInterceptorChain(
  private val interceptors: List<Interceptor>,
  private val transmitter: Transmitter,
  private val exchange: Exchange?,
  private val index: Int,
  private val request: Request,
  private val call: Call,
  private val connectTimeout: Int,
  private val readTimeout: Int,
  private val writeTimeout: Int
) : Interceptor.Chain {

  private var calls: Int = 0

    ......
  override fun proceed(request: Request): Response {
    return proceed(request, transmitter, exchange)
  }

  @Throws(IOException::class)
  fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
    if (index >= interceptors.size) throw AssertionError()

    calls++

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.exchange != null && !this.exchange.connection().supportsUrl(request.url())) {
      throw IllegalStateException("network interceptor " + interceptors[index - 1] +
          " must retain the same host and port")
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.exchange != null && calls > 1) {
      throw IllegalStateException("network interceptor " + interceptors[index - 1] +
          " must call proceed() exactly once")
    }

    // Call the next interceptor in the chain.
    val next = RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (exchange != null && index + 1 < interceptors.size && next.calls != 1) {
      throw IllegalStateException("network interceptor " + interceptor +
          " must call proceed() exactly once")
    }

    if (response.body() == null) {
      throw IllegalStateException(
          "interceptor $interceptor returned a response with no body")
    }

    return response
  }
}

這一段代碼首先對(duì)index進(jìn)行了檢查,然后對(duì)call,exchange中的種種參數(shù)進(jìn)行了檢查空镜。最后調(diào)用了

    // Call the next interceptor in the chain.
    val next = RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

調(diào)用當(dāng)前interceptor的intercept方法浩淘,并將下一個(gè)interceptor傳入。

小結(jié)

okhttp的網(wǎng)絡(luò)請(qǐng)求姑裂,采用了interceptor這樣的結(jié)構(gòu)馋袜,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求是一個(gè)層級(jí)深,分支少的結(jié)構(gòu)舶斧。每一個(gè)層級(jí)并不關(guān)心下一個(gè)層級(jí)的實(shí)現(xiàn)欣鳖。因此,這樣的結(jié)構(gòu)很合適茴厉。

發(fā)散一下泽台,對(duì)于層級(jí)深,分支少矾缓,交付結(jié)果一致的業(yè)務(wù)模型怀酷,我們也可以采用這種interceptor的模型。方便層級(jí)之前解耦合嗜闻。

如有問(wèn)題蜕依,歡迎指正。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琉雳,一起剝皮案震驚了整個(gè)濱河市样眠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翠肘,老刑警劉巖檐束,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異束倍,居然都是意外死亡被丧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門绪妹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甥桂,“玉大人,你說(shuō)我怎么就攤上這事邮旷「襦遥” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵廊移,是天一觀的道長(zhǎng)糕簿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)狡孔,這世上最難降的妖魔是什么懂诗? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮苗膝,結(jié)果婚禮上殃恒,老公的妹妹穿的比我還像新娘。我一直安慰自己辱揭,他們只是感情好离唐,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著问窃,像睡著了一般亥鬓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上域庇,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天嵌戈,我揣著相機(jī)與錄音,去河邊找鬼听皿。 笑死熟呛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尉姨。 我是一名探鬼主播庵朝,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼又厉!你這毒婦竟也來(lái)了九府?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馋没,失蹤者是張志新(化名)和其女友劉穎昔逗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篷朵,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勾怒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了声旺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笔链。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腮猖,靈堂內(nèi)的尸體忽然破棺而出鉴扫,到底是詐尸還是另有隱情,我是刑警寧澤澈缺,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布坪创,位于F島的核電站炕婶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏莱预。R本人自食惡果不足惜柠掂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望依沮。 院中可真熱鬧涯贞,春花似錦、人聲如沸危喉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辜限。三九已至皇拣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間列粪,已是汗流浹背审磁。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岂座,地道東北人态蒂。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像费什,于是被迫代替她去往敵國(guó)和親钾恢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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