網(wǎng)絡(luò)框架-OkHttp源碼解析

不開心竹勉,就算長生不老也沒用,開心恐锦,就算只能活幾天也足夠!——《大話西游之月光寶盒》

前言

本文從源碼角度分析OkHttp的工作流程疆液,源碼基于4.4.0一铅。

OkHttp基本實現(xiàn)原理

OkHttp的內(nèi)部實現(xiàn)通過一個責(zé)任鏈模式完成,將網(wǎng)絡(luò)請求的各個階段封裝到各個鏈條中堕油,實現(xiàn)了各層的解耦潘飘。

OkHttp使用流程

第一步:創(chuàng)建OkHttpClient

OkHttpClient是okhttp3框架的客戶端,用于發(fā)送http請求(Requests)和讀取讀取網(wǎng)絡(luò)返回數(shù)據(jù)(Responses)掉缺。因為OkHttpClient擁有自己的連接池和線程池卜录,所以官方建議使用單例創(chuàng)建OkHttpClient,即一個進程中只創(chuàng)建一次即可眶明,以后的每次網(wǎng)絡(luò)請求都使用該實例艰毒。這樣做利于減少延遲和節(jié)省內(nèi)存。
創(chuàng)建OkHttpClient的兩種方式:

//方式一:使用默認網(wǎng)絡(luò)配置創(chuàng)建OkHttpClient實例
OkHttpClient client = new OkHttpClient();

//方式二:通過構(gòu)建者模式自定義網(wǎng)絡(luò)配置創(chuàng)建OkHttpClient實例
 OkHttpClient client = new OkHttpClient.Builder()
                ...//通過Builder提供的方法設(shè)置網(wǎng)絡(luò)參數(shù)
                .build();

OkHttpClient.Builder類的部分變量如下所示,通過其提供的方法可以設(shè)置部分參數(shù)的值

   //調(diào)度器搜囱,實現(xiàn)同步 /異步的關(guān)鍵丑瞧,后文會進行講解
    internal var dispatcher: Dispatcher = Dispatcher()
    //連接池
    internal var connectionPool: ConnectionPool = ConnectionPool()
    //各種攔截器
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    //連接失敗后自動重試,默認true
    internal var retryOnConnectionFailure = true
    internal var authenticator: Authenticator = Authenticator.NONE
    internal var followRedirects = true
    internal var followSslRedirects = true
    //網(wǎng)絡(luò)請求Cookie
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    //網(wǎng)絡(luò)請求的緩存
    internal var cache: Cache? = null
    internal var dns: Dns = Dns.SYSTEM
    internal var proxy: Proxy? = null
    internal var proxySelector: ProxySelector? = null
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0
    //網(wǎng)絡(luò)連接超時時間
    internal var connectTimeout = 10_000
    //數(shù)據(jù)讀取超時時間
    internal var readTimeout = 10_000
    //數(shù)據(jù)寫入超時時間
    internal var writeTimeout = 10_000
    internal var pingInterval = 0
    internal var routeDatabase: RouteDatabase? = null

第二步:創(chuàng)建網(wǎng)絡(luò)請求描述對象Request

Request對象通過參數(shù)設(shè)置用于描述一次網(wǎng)絡(luò)請求犬辰。它的實例創(chuàng)建同樣采用了構(gòu)建者模式嗦篱,創(chuàng)建Request實例的代碼如下:

Request request = new Request.Builder().
                url("換成要請求的url地址").
                ...//通過Builder提供的方法設(shè)置網(wǎng)絡(luò)參數(shù)
                build();

Request.Builder()的部分源碼如下:

public open class Builder {
        public constructor() { /* compiled code */ }

        internal constructor(request: okhttp3.Request) { /* compiled code */ }

        internal final var body: okhttp3.RequestBody? /* compiled code */

        internal final var headers: okhttp3.Headers.Builder /* compiled code */

        internal final var method: kotlin.String /* compiled code */

        internal final var url: okhttp3.HttpUrl? /* compiled code */

       ...//省略了部分對外提供的參數(shù)設(shè)置方法
    }

可見通過Request.Builder()可以設(shè)置如下常用的網(wǎng)絡(luò)參數(shù):
headers:請求頭;
body:請求體幌缝;
method:請求方式,比如:get/post/put...等請求方式诫欠;
url:請求地址涵卵;

第三步:創(chuàng)建網(wǎng)絡(luò)請求對象Call

Call為一個接口浴栽,其實現(xiàn)類為RealCall。一個Call對象封裝表示了一次網(wǎng)絡(luò)請求轿偎,通過它能夠執(zhí)行或者取消一次網(wǎng)絡(luò)請求典鸡,并且每一次網(wǎng)絡(luò)請求都會生產(chǎn)一個新的Call對象,同一個Call對象不能被執(zhí)行兩次坏晦。Call對象的創(chuàng)建需要用到前面創(chuàng)建好的OkHttpClient對象的newCall(Request request)方法萝玷,并需要將第二步創(chuàng)建好的用于描述此次網(wǎng)絡(luò)請求的Request對象作為參數(shù)傳進去,整體代碼如下:

OkHttpClient client = new OkHttpClient.Builder()
                ...//通過Builder提供的方法設(shè)置網(wǎng)絡(luò)參數(shù)
                .build();
Request request = new Request.Builder().
                url("換成要請求的url地址").
                ...//通過Builder提供的方法設(shè)置網(wǎng)絡(luò)參數(shù)
                build();
Call call = client.newCall(request);

我們來看OkHttpClient的newCall(Request request)方法昆婿,其源碼如下:

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

可見最后創(chuàng)建的是RealCall對象球碉。

第四步:通過Call對象執(zhí)行網(wǎng)絡(luò)請求

okhttp3中提供了兩種網(wǎng)絡(luò)請求方式:一種是同步請求,調(diào)用call.execute()方法仓蛆;另一種是異步請求睁冬,調(diào)用call.enqueue(Callback callback)方法。在分析兩種請求方式之前看疙,先來看看OkHttp中一個重要成員Dispatcher(調(diào)度器)豆拨。

Dispatcher(調(diào)度器)

Dispatcher是OkHttp的任務(wù)調(diào)度的核心類,負責(zé)管理同步和異步的請求能庆,并管理每一個請求任務(wù)的請求狀態(tài)施禾,其內(nèi)部維護了一個線程池用于執(zhí)行相應(yīng)的請求。Dispatcher定義默認的最大并發(fā)請求量 maxRequests = 64 和單個host支持的最大并發(fā)量 maxRequestsPerHost = 5搁胆。并且在其內(nèi)部維護了三個雙端阻塞隊列用于管理網(wǎng)絡(luò)任務(wù)拾积,代碼如下:

class Dispatcher constructor() {
...
//默認的最大并發(fā)請求量為64
@get:Synchronized var maxRequests = 64
...
//默認單個host支持的最大并發(fā)量為5
@get:Synchronized var maxRequestsPerHost = 5
...
  /** 存儲準備好的異步調(diào)用任務(wù),它們將順序執(zhí)行 */
  private val readyAsyncCalls = ArrayDeque<AsyncCall>()

  /**存儲異步運行中的網(wǎng)絡(luò)任務(wù)丰涉,包括已經(jīng)被取消但還未結(jié)束的網(wǎng)絡(luò)任務(wù)*/
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()

  /**存儲同步運行中的網(wǎng)絡(luò)任務(wù)拓巧,包括已經(jīng)被取消但還未結(jié)束的網(wǎng)絡(luò)任務(wù)*/
  private val runningSyncCalls = ArrayDeque<RealCall>()
...
}

關(guān)于這3個雙端阻塞隊列的作用,可以這么理解:把Dispatcher當成生產(chǎn)者一死,把線程池當成消費者肛度,當生產(chǎn)者生產(chǎn)的網(wǎng)絡(luò)請求數(shù)量大于消費者所能承受的最大范圍,就把未能及時執(zhí)行的任務(wù)保存在readyAsyncCalls隊列中投慈,當時機成熟承耿,也就是線程池有空余線程可以執(zhí)行時,會調(diào)用promoteCall()這個方法把等待隊列中的任務(wù)取出放到線程池中執(zhí)行伪煤,并且把這個任務(wù)轉(zhuǎn)移到runningAsyncCalls或者runningSyncCalls 隊列中去加袋。

為什么要使用雙端隊列?很簡單因為網(wǎng)絡(luò)請求執(zhí)行順序跟排隊一樣抱既,講究先來后到职烧,新來的請求放隊尾,執(zhí)行請求從對頭部取。我們知道LinkedList同樣也實現(xiàn)了Deque接口蚀之,內(nèi)部是用鏈表實現(xiàn)的雙端隊列蝗敢,那為什么不用LinkedList呢?實際上這與readyAsyncCalls向runningAsyncCalls轉(zhuǎn)換有關(guān),當執(zhí)行完一個請求或調(diào)用enqueue方法入隊新的請求時,會對readyAsyncCalls進行一次遍歷庙楚,將那些符合條件的等待請求轉(zhuǎn)移到runningAsyncCalls隊列中并交給線程池執(zhí)行。盡管二者都能完成這項任務(wù)讶泰,但是由于鏈表的數(shù)據(jù)結(jié)構(gòu)致使元素離散的分布在內(nèi)存的各個位置,CPU緩存無法帶來太多的便利拂到,另外在垃圾回收時痪署,使用數(shù)組結(jié)構(gòu)的效率要優(yōu)于鏈表。

接下來咱們結(jié)合上面說的Dispatcher分別看看同步請求和異步請求的實現(xiàn)過程谆焊,并詳細說一下他們是如何實現(xiàn)的惠桃。
請求方式一:同步請求
call.execute()源碼如下:

override fun execute(): Response {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    timeout.enter()
    callStart()
    try {
      //①調(diào)用調(diào)度器dispatcher的executed(Call call)方法
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      //②調(diào)用分配器Dispatcher的finish()方法
      client.dispatcher.finished(this)
    }
  }

注釋①部分調(diào)用了 client.dispatcher的executed()方法,再來看client.dispatcher的executed(call: RealCall)方法:

@Synchronized internal fun executed(call: RealCall) {
    runningSyncCalls.add(call)
  }

由此可見辖试,注釋①部分是將這次網(wǎng)絡(luò)請求RealCall實例存進了runningSyncCalls這雙端阻塞隊列辜王;
接著方法又調(diào)用了getResponseWithInterceptorChain()方法并返回。關(guān)于getResponseWithInterceptorChain()先放一邊罐孝,在異步請求中再一起講解呐馆。

接下來看注釋②,不管結(jié)果交易結(jié)果如何莲兢,都會調(diào)用finally中的client.dispatcher.finished(this)將本次請求從隊列中移除汹来。
請求方式二:異步請求
call.enqueue(Callback callback)源碼如下:

override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    callStart()
    //①調(diào)用調(diào)度器dispatcher的enqueue(AsyncCall call)方法
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

可見其內(nèi)部調(diào)用了client.dispatcher.enqueue(AsyncCall call)方法,分析該方法之前先來看看其參數(shù)AsyncCall類的定義:

internal 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)
        }
      }
    }
  }

跟蹤源碼發(fā)現(xiàn)AsyncCall是RealCall的一個內(nèi)部類改艇,其實現(xiàn)了Runnable接口收班,所以AsyncCall就是一個Runnable的實現(xiàn),當在線程池中執(zhí)行該對象時就會執(zhí)行其實現(xiàn)的run()方法谒兄,通過注釋①處源碼了解到和同步請求方法一樣它也會去調(diào)用getResponseWithInterceptorChain()方法摔桦。

現(xiàn)在回到client.dispatcher.enqueue(AsyncCall call)方法,其源碼如下:

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

      // ①嘗試判斷并復(fù)用同一host請求的實例
      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

可見方法會先將請求放入readyAsyncCalls雙端阻塞隊列承疲,然后會在注釋①出嘗試判斷并復(fù)用同一host請求的實例邻耕,findExistingCallWithHost(String host)源碼如下:

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
  }

其作用為循環(huán)遍歷readyAsyncCalls和runningAsyncCalls列表尋找host值相同的call實例。如果找到Call實例就會調(diào)用call.reuseCallsPerHostFrom(AsyncCall other)方法燕鸽,其源碼如下:

internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
@Volatile var callsPerHost = AtomicInteger(0)
      private set

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

其作用為給AsyncCall實例的成員變量callsPerHost賦值兄世,即共享同一host下AsyncCall請求實例的AtomicInteger值。

接著client.dispatcher.enqueue( AsyncCall call)最終會調(diào)用promoteAndExecute()方法啊研,其源碼如下:

private fun promoteAndExecute(): Boolean {
    this.assertThreadDoesntHoldLock()

    val executableCalls = mutableListOf<AsyncCall>()
    val isRunning: Boolean
    synchronized(this) {
      val i = readyAsyncCalls.iterator()
      //遍歷readyAsyncCalls
      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.
        //符合條件 從readyAsyncCalls列表中刪除
        i.remove()
        //per host 計數(shù)加1
        asyncCall.callsPerHost.incrementAndGet()
        executableCalls.add(asyncCall)
        //移入runningAsyncCalls列表
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

    for (i in 0 until executableCalls.size) {
      val asyncCall = executableCalls[i]
      //①提交任務(wù)到線程池
      asyncCall.executeOn(executorService)
    }

    return isRunning
  }

可見其主要邏輯為遍歷readyAsyncCalls隊列御滩,其中滿足閾值校驗的AsyncCall對象被移至runningAsyncCalls隊列同時提交到線程池執(zhí)行鸥拧。注釋①中可見提交到線程池使用到了executorService對象,現(xiàn)在來看看executorService的定義:

@get:Synchronized
  @get:JvmName("executorService") val executorService: ExecutorService
    get() {
      if (executorServiceOrNull == null) {
        executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
            SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
      }
      return executorServiceOrNull!!
    }

這不是一個newCachedThreadPool嗎艾恼?沒錯住涉,除了最后一個threadFactory參數(shù)之外與newCachedThreadPool的源碼實現(xiàn)一模一樣麸锉,只不過是多設(shè)置了線程名字方便排查問題钠绍。其阻塞隊列采用的SynchronousQueue,它的特點是不存儲數(shù)據(jù)花沉,當添加一個元素時柳爽,必須等待一個消費線程取出它,否則一直阻塞碱屁,如果當前有空閑線程則直接在這個空閑線程執(zhí)行磷脯,如果沒有則新啟動一個線程執(zhí)行任務(wù)。通常用于需要快速響應(yīng)任務(wù)的場景娩脾,在網(wǎng)絡(luò)請求要求低延遲的大背景下比較合適赵誓。可見這個executorService對象就是Dispatcher自身維護的用于執(zhí)行網(wǎng)絡(luò)任務(wù)的線程池柿赊。
接著來看注釋①中執(zhí)行的AsyncCall.executeOn(ExecutorService executorService)方法的源碼:

    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!
        }
      }
    }

可見其功能為將AsyncCall對象加入到executorService線程池并執(zhí)行俩功,并在線程結(jié)束或者執(zhí)行失敗后清除AsyncCall請求對象。通過上面對AsyncCall類的分析可知碰声,AsyncCall類是一個Runnable的實現(xiàn)诡蜓,將asyncCall對象加入線程池執(zhí)行會去執(zhí)行其內(nèi)部實現(xiàn)的run()方法并最終調(diào)getResponseWithInterceptorChain()

法來獲取網(wǎng)絡(luò)請求的返回值。AsyncCall內(nèi)部實現(xiàn)的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)
        }
      }
    }

在分析獲取網(wǎng)絡(luò)請求返回值getResponseWithInterceptorChain()方法之前再插一句胰挑,通過上面的分析蔓罚,不管同步請求還是異步請求,在請求正痴八蹋或者異常結(jié)束后都會調(diào)用了client.dispatcher.finished(call: RealCall)或client.dispatcher.finished(call: AsyncCall)方法豺谈,那我們就來看看其源碼做了什么:

  //異步任務(wù)執(zhí)行結(jié)束
  internal fun finished(call: AsyncCall) {
    call.callsPerHost.decrementAndGet()
    finished(runningAsyncCalls, call)
  }

  //同步任務(wù)執(zhí)行結(jié)束
  internal fun finished(call: RealCall) {
    finished(runningSyncCalls, call)
  }
  //同步異步任務(wù) 統(tǒng)一匯總到這里
  private fun <T> finished(calls: Deque<T>, call: T) {
    val idleCallback: Runnable?
    synchronized(this) {
      //將完成的任務(wù)從隊列中刪除
      if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
      idleCallback = this.idleCallback
    }
    //這個方法在前面已經(jīng)分析過了,用于將readyAsyncCalls隊列中的請求移入runningAsyncCalls隊列中贡这,并交由線程池執(zhí)行茬末。
    val isRunning = promoteAndExecute()
    //如果沒有請求需要執(zhí)行,回調(diào)閑置callback
    if (!isRunning && idleCallback != null) {
      idleCallback.run()
    }
  }

方法的主要邏輯已在注釋中寫出藕坯,需要注意的是promoteAndExecute()方法在enqueue和finish方法中都會調(diào)用团南,即當有新的請求入隊和當前請求完成后,需要重新提交一遍任務(wù)到線程池炼彪。
到目前為止我們通過源碼分析了解到吐根,其實OkHttp的同步請求call.execute()和異步請求call.enqueue(Callback callback)其實都是通過OkHttpClient中維護的Dispatcher調(diào)度器對象來實現(xiàn)的,而異步任務(wù)又是隊列的方式在Dispatcher調(diào)度器對象自身維護的ExecutorService線程池對象中按順序執(zhí)行的辐马。不同網(wǎng)絡(luò)任務(wù)的執(zhí)行狀態(tài)又分別保存在Dispatcher調(diào)度器對象自身維護的3個不同的雙端阻塞隊列中拷橘。

下面終于到了OkHttp原理的重點和難點部分了,OkHttp的內(nèi)部實現(xiàn)通過一個責(zé)任鏈模式完成,將網(wǎng)絡(luò)請求的各個階段封裝到各個鏈條中冗疮,實現(xiàn)了各層的解耦萄唇。而這個責(zé)任鏈模式的具體實現(xiàn)就在getResponseWithInterceptorChain()方法中。

getResponseWithInterceptorChain方法(OKHTTP責(zé)任鏈模式的實現(xiàn))

getResponseWithInterceptorChain方法是整個OkHttp實現(xiàn)責(zé)任鏈模式的核心术幔。下面來看看其源碼:

@Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    //創(chuàng)建攔截器數(shù)組
    val interceptors = mutableListOf<Interceptor>()
    //添加應(yīng)用攔截器
    interceptors += client.interceptors
    //添加重試和重定向攔截器
    interceptors += RetryAndFollowUpInterceptor(client)
    //添加橋接攔截器
    interceptors += BridgeInterceptor(client.cookieJar)
    //添加緩存攔截器
    interceptors += CacheInterceptor(client.cache)
    //添加連接攔截器
    interceptors += ConnectInterceptor
    if (!forWebSocket) {
      //添加網(wǎng)絡(luò)攔截器
      interceptors += client.networkInterceptors
    }
    //添加請求攔截器
    interceptors += CallServerInterceptor(forWebSocket)
    //創(chuàng)建責(zé)任鏈
    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 {
      //啟動責(zé)任鏈
      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)
      }
    }
  }

這里我們先不關(guān)心每個攔截器具體作用是什么另萤,主流程最終走到chain.proceed(originalRequest)。我們先來看一下這個procceed方法:

@Throws(IOException::class)
  override fun proceed(request: Request): Response {
    check(index < interceptors.size)
    // 統(tǒng)計當前攔截器調(diào)用proceed方法的次數(shù)
    calls++

    if (exchange != null) {
      // exchage是對請求流的封裝诅挑,在執(zhí)行ConnectInterceptor前為空四敞,連接和流已經(jīng)建立但此時此連接不再支持當前url說明之前的網(wǎng)絡(luò)攔截器對url或端口進行了修改,這是不允許的!!
      check(exchange.connection.supportsUrl(request.url)) {
        "network interceptor ${interceptors[index - 1]} must retain the same host and port"
      }
      // 這里是對攔截器調(diào)用proceed方法的限制拔妥,在ConnectInterceptor及其之后的攔截器最多只能調(diào)用一次proceed!!
      check(calls == 1) {
        "network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
      }
    }

    // 創(chuàng)建下一層責(zé)任鏈 注意index + 1
    val next = copy(index = index + 1, request = request)
    //取出下標為index的攔截器忿危,并調(diào)用其intercept方法,將新建的鏈傳入没龙。
    val interceptor = interceptors[index]
    @Suppress("USELESS_ELVIS")
    val response = interceptor.intercept(next) ?: throw NullPointerException(
        "interceptor $interceptor returned null")

    if (exchange != null) {
      // 保證在ConnectInterceptor及其之后的攔截器至少調(diào)用一次proceed!!
      check(index + 1 >= interceptors.size || next.calls == 1) {
        "network interceptor $interceptor must call proceed() exactly once"
      }
    }
    //保證未返回含有空body值的response
    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
  }

代碼中的注釋已經(jīng)寫得比較清楚了铺厨,總結(jié)起來就是創(chuàng)建下一級責(zé)任鏈,然后取出當前攔截器硬纤,調(diào)用其intercept方法并傳入創(chuàng)建的責(zé)任鏈解滓。++為保證責(zé)任鏈能依次進行下去,必須保證除最后一個攔截器(CallServerInterceptor)外咬摇,其他所有攔截器intercept方法內(nèi)部必須調(diào)用一次chain.proceed()方法++伐蒂,如此一來整個責(zé)任鏈就運行起來了。
比如ConnectInterceptor源碼中:

object ConnectInterceptor : Interceptor {
  @Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    //創(chuàng)建連接和流   
    val realChain = chain as RealInterceptorChain
    val exchange = realChain.call.initExchange(chain)
    val connectedChain = realChain.copy(exchange = exchange)
    //執(zhí)行下一級責(zé)任鏈
    return connectedChain.proceed(realChain.request)
  }
}

除此之外在責(zé)任鏈不同節(jié)點對于proceed的調(diào)用次數(shù)有不同的限制肛鹏,ConnectInterceptor攔截器及其之后的攔截器能且只能調(diào)用一次逸邦,因為網(wǎng)絡(luò)握手、連接在扰、發(fā)送請求的工作發(fā)生在這些攔截器內(nèi)缕减,表示正式發(fā)出了一次網(wǎng)絡(luò)請求;而在這之前的攔截器可以執(zhí)行多次proceed芒珠,比如錯誤重試桥狡。

經(jīng)過責(zé)任鏈一級一級的遞推下去,最終會執(zhí)行到CallServerInterceptor的intercept方法皱卓,此方法會將網(wǎng)絡(luò)響應(yīng)的結(jié)果封裝成一個Response對象并return裹芝。之后沿著責(zé)任鏈一級一級的回溯,最終就回到getResponseWithInterceptorChain方法的返回娜汁。

攔截器分類

大致總結(jié)責(zé)任鏈的各個節(jié)點攔截器的作用:

攔截器 作用
應(yīng)用攔截器 拿到的是原始請求嫂易,可以添加一些自定義header、通用參數(shù)掐禁、參數(shù)加密怜械、網(wǎng)關(guān)接入等等颅和。
RetryAndFollowUpInterceptor 處理錯誤重試和重定向
BridgeInterceptor 應(yīng)用層和網(wǎng)絡(luò)層的橋接攔截器,主要工作是為請求添加cookie缕允、添加固定的header峡扩,比如Host、Content-Length障本、Content-Type教届、User-Agent等等,然后保存響應(yīng)結(jié)果的cookie彼绷,如果響應(yīng)使用gzip壓縮過巍佑,則還需要進行解壓茴迁。
CacheInterceptor 緩存攔截器寄悯,如果命中緩存則不會發(fā)起網(wǎng)絡(luò)請求。
ConnectInterceptor 連接攔截器堕义,內(nèi)部會維護一個連接池猜旬,負責(zé)連接復(fù)用、創(chuàng)建連接(三次握手等等)倦卖、釋放連接以及創(chuàng)建連接上的socket流洒擦。
networkInterceptors(網(wǎng)絡(luò)攔截器) 用戶自定義攔截器,通常用于監(jiān)控網(wǎng)絡(luò)層的數(shù)據(jù)傳輸怕膛。
CallServerInterceptor 請求攔截器熟嫩,在前置準備工作完成后,真正發(fā)起了網(wǎng)絡(luò)請求褐捻。

參考文章
https://blog.csdn.net/qq_29152241/article/details/82011539
http://www.reibang.com/p/8bcfb10243a1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掸茅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子柠逞,更是在濱河造成了極大的恐慌昧狮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件板壮,死亡現(xiàn)場離奇詭異逗鸣,居然都是意外死亡,警方通過查閱死者的電腦和手機绰精,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門撒璧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笨使,你說我怎么就攤上這事卿樱。” “怎么了阱表?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵殿如,是天一觀的道長贡珊。 經(jīng)常有香客問我,道長涉馁,這世上最難降的妖魔是什么门岔? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮烤送,結(jié)果婚禮上寒随,老公的妹妹穿的比我還像新娘。我一直安慰自己帮坚,他們只是感情好妻往,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著试和,像睡著了一般讯泣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上阅悍,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天好渠,我揣著相機與錄音,去河邊找鬼节视。 笑死拳锚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的寻行。 我是一名探鬼主播霍掺,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拌蜘!你這毒婦竟也來了杆烁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拦坠,失蹤者是張志新(化名)和其女友劉穎连躏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贞滨,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡入热,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了晓铆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺良。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖骄噪,靈堂內(nèi)的尸體忽然破棺而出尚困,到底是詐尸還是另有隱情,我是刑警寧澤链蕊,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布事甜,位于F島的核電站谬泌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逻谦。R本人自食惡果不足惜掌实,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望邦马。 院中可真熱鬧贱鼻,春花似錦、人聲如沸滋将。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽随闽。三九已至父丰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間橱脸,已是汗流浹背础米。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留添诉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓医寿,卻偏偏與公主長得像栏赴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子靖秩,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

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