OkHttp4.3源碼解析之 - 發起請求

什麼是OkHttp?

OkHttp在Android開發領域裏面應該是無人不知了吧。它是一個由Square公司開源的第三方庫。主要用於處理網絡請求。

OkHttp Github地址:https://github.com/square/okhttp/

關於Square公司,大家可以去看看他們的其他開源庫,質量都挺高的,不僅包括Android相關的,還有Go,Ruby,JS,Kotlin等等

Square官方網站:https://square.github.io/

到目前爲止,OkHttp的最新版本是4.3.1,裏面一部分代碼也已經用Kotlin來重寫了,而大部分分析OkHttp源碼的文章還停留在以前舊的版本,因此在寫這系列源碼分析的文章同時,也是學習Kotlin的一個好機會。

從一個簡單的請求說起

如果有做過Android開發的話,相信以下代碼大家都很熟悉了:

        val client = OkHttpClient()
        val request = Request.Builder().url("https://www.baidu.com").build()

        val response = client.newCall(request).enqueue(object: Callback{
            override fun onFailure(call: Call, e: IOException) {
                println("bluelzy --- ${e.message}")
            }

            override fun onResponse(call: Call, response: Response) {
                println("bluelzy --- ${response.body.toString()}")
            }

        })

這段代碼做的事情很簡單:

  1. 創建一個OkHttpClient對象

  2. 使用建造者模式創建一個Request對象

  3. 使用OkHttpClient.newCall(request).enqueue() 發起請求,並處理回調

但是這短短的幾行代碼裏面,就已經有很多我們可以學習的東西了。

首先是建造者模式,也叫Builder模式,它的作用是讓用戶自由組合需要的參數,來實現不同的需求。具體實現方法可以通過接口,也可以像OkHttp一樣,使用內部類。這裏我們不詳細展開闡述,有興趣的讀者可以去看看《大話設計模式》或者《Android源碼設計模式分析與實戰》。

然後,通過val這種寫法來定義變量也不太優雅,因爲這些變量其實聲明一次就夠了,我們可以改進一下代碼:

// 定義Request
Request.Builder().url("https://www.baidu.com").build().let { request ->
            // 定義OkHttpClient 
            OkHttpClient().newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    println("bluelzy --- ${e.message}")
                }

                override fun onResponse(call: Call, response: Response) {
                    println("bluelzy --- ${response.body.toString()}")
                }

            })
        }

把聲明的三個變量都幹掉,這樣代碼看起來是不是簡潔多了?

上面就是一個最簡單的OkHttp發起GET請求的方式,下面我們一起來看看OkHttp是怎麼做到的。

1.創建OkHttpClient

這個類的作用就是發起HTTP請求和接收響應

首先OkHttpClient有兩個構造方法,一個是無參構造方法,另外一個是傳入參數爲Builder的構造方法

OkHttpClient的構造方法

無參構造方法

constructor() : this(Builder())

有參構造方法

 open class OkHttpClient internal constructor(
  builder: Builder
) 

OkHttpClient.Builder類

可以看到,無參構造方法其實也是調用了Builder爲參數的構造方法,這裏傳入的Builder() 就是默認的實現,繼續看看Builder裏面是怎麼實現的:

 class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()  // 調度器
    internal var connectionPool: ConnectionPool = ConnectionPool()   // 連接池
    internal val interceptors: MutableList<Interceptor> = mutableListOf()//攔截器
    // 網絡攔截器
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
     // 事件監聽
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    internal var retryOnConnectionFailure = true
    internal var authenticator: Authenticator = Authenticator.NONE
    internal var followRedirects = true
    internal var followSslRedirects = true
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
     // 緩存
    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
     // Socket工廠
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
     // SSL Socket工廠,用於HTTPS
    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
    // 驗證確認響應鏈,用於HTTPS
     internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0
    internal var connectTimeout = 10_000
    internal var readTimeout = 10_000
    internal var writeTimeout = 10_000
    internal var pingInterval = 0

    internal constructor(okHttpClient: OkHttpClient) : this() {
      this.dispatcher = okHttpClient.dispatcher
      this.connectionPool = okHttpClient.connectionPool
      this.interceptors += okHttpClient.interceptors
      this.networkInterceptors += okHttpClient.networkInterceptors
      this.eventListenerFactory = okHttpClient.eventListenerFactory
      this.retryOnConnectionFailure = okHttpClient.retryOnConnectionFailure
      this.authenticator = okHttpClient.authenticator
      this.followRedirects = okHttpClient.followRedirects
      this.followSslRedirects = okHttpClient.followSslRedirects
      this.cookieJar = okHttpClient.cookieJar
      this.cache = okHttpClient.cache
      this.dns = okHttpClient.dns
      this.proxy = okHttpClient.proxy
      this.proxySelector = okHttpClient.proxySelector
      this.proxyAuthenticator = okHttpClient.proxyAuthenticator
      this.socketFactory = okHttpClient.socketFactory
      this.sslSocketFactoryOrNull = okHttpClient.sslSocketFactoryOrNull
      this.x509TrustManagerOrNull = okHttpClient.x509TrustManager
      this.connectionSpecs = okHttpClient.connectionSpecs
      this.protocols = okHttpClient.protocols
      this.hostnameVerifier = okHttpClient.hostnameVerifier
      this.certificatePinner = okHttpClient.certificatePinner
      this.certificateChainCleaner = okHttpClient.certificateChainCleaner
      this.callTimeout = okHttpClient.callTimeoutMillis
      this.connectTimeout = okHttpClient.connectTimeoutMillis
      this.readTimeout = okHttpClient.readTimeoutMillis
      this.writeTimeout = okHttpClient.writeTimeoutMillis
      this.pingInterval = okHttpClient.pingIntervalMillis
    }

Builder是OkHttpClient的內部類,然後Builder聲明瞭很多的參數,這些參數的作用我會在後面的文章中詳細分析。

到這裏爲止,我們就完成了第一步,創建一個OkHttpClient對象,在構造方法裏面創建了Builder()對象。然後看第二步:Request.Builder() 創建Request對象

2.創建Request對象

Request的構造方法

Request的構造方法需要4個參數

class Request internal constructor(
  @get:JvmName("url") val url: HttpUrl,  // 請求的url 
  @get:JvmName("method") val method: String, // 請求方法類型
  @get:JvmName("headers") val headers: Headers, // 請求頭
  @get:JvmName("body") val body: RequestBody?, // 請求體
  internal val tags: Map<Class<*>, Any>
) 

Request.Builder類

Request同樣使用了Builder模式,我們直接看Request.Builder類:

open class Builder {
    internal var url: HttpUrl? = null
    internal var method: String
    internal var headers: Headers.Builder
    internal var body: RequestBody? = null

    /** A mutable map of tags, or an immutable empty map if we don't have any. */
    internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()

	// 無參構造方法
    constructor() {
      // 默認爲GET 
      this.method = "GET"
      this.headers = Headers.Builder()
    }

	// 有參構造方法
    internal constructor(request: Request) {
      this.url = request.url
      this.method = request.method
      this.body = request.body
      this.tags = if (request.tags.isEmpty()) {
        mutableMapOf()
      } else {
        request.tags.toMutableMap()
      }
      this.headers = request.headers.newBuilder()
    }

    open fun url(url: HttpUrl): Builder = apply {
      this.url = url
    }
    // 省略代碼
}

可以看到,Builder默認的請求方法是GET,並且初始化了一個大小爲20的ArrayList作爲Headers的容器

除了method和haders之外,還有兩個關鍵的參數,一個是url,另外一個就是RequestBody。這幾個參數都提供了set方法,支持鏈式調用,例如url:

open fun url(url: String): Builder {
      // Silently replace web socket URLs with HTTP URLs.
      val finalUrl: String = when {
        url.startsWith("ws:", ignoreCase = true) -> {
          "http:${url.substring(3)}"
        }
        url.startsWith("wss:", ignoreCase = true) -> {
          "https:${url.substring(4)}"
        }
        else -> url
      }

      return url(finalUrl.toHttpUrl())
    }

這裏做了一點處理,如果是基於web socket協議的url,會被替換成http,而wws則是加密的web socket協議。

設置完了url / requestBody / mothod / headers 之後,我們都會調用build()方法:

    open fun build(): Request {
      return Request(
          checkNotNull(url) { "url == null" },
          method,
          headers.build(),
          body,
          tags.toImmutableMap()
      )
    }

其實就是把剛剛設置的作爲參數,調用Request的有參構造方法,創建一個Request對象。到這裏爲止,第二步也就完成了。

3.發起請求

做完前面兩步之後,終於到了激動人心的時刻了,我們通過OkHttpClient.newCall(request).enqueue()來發起請求

到源碼裏面看看newCall和enqueue這兩個方法分別做了什麼

newCall()方法

  override fun newCall(request: Request): Call {
    return RealCall.newRealCall(this, request, forWebSocket = false)
  }

調用RealCall.newRealCall方法,並且傳入了OkHttpClient和Request作爲參數

再看看newRealCall:

  companion object {
    fun newRealCall(
      client: OkHttpClient,
      originalRequest: Request,
      forWebSocket: Boolean
    ): RealCall {
      // Safely publish the Call instance to the EventListener.
      return RealCall(client, originalRequest, forWebSocket).apply {
        transmitter = Transmitter(client, this)  // okhttp和網絡層的中介
      }
    }
  }

其實就是創建了一個RealCall對象,注意這裏同時創建了一個Transmitter對象。它後面會再出現的,暫時先忽略,我們先回到發起請求這個過程中來。

enqueue()方法

上一步創建了RealCall對象是吧,傳入了OkHttpClient和Request對象是吧,那麼enqueue()方法又做了什麼呢?

這個enqueue()方法其實是Call接口的其中一個方法,除了enqueue之外,還有request(), execute()等其他方法。我們先看這個enqueue()方法:

  override fun enqueue(responseCallback: Callback) {
    synchronized(this) {
      check(!executed) { "Already Executed" }
      executed = true
    }
    transmitter.callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

  override fun cancel() {
    transmitter.cancel()
  }

可以看到,首先這個方法有一個Callback作爲參數,而這個Callback接口裏面又有兩個方法:

interface Callback {
  /**
   * Called when the request could not be executed due to cancellation, a connectivity problem or
   * timeout. Because networks can fail during an exchange, it is possible that the remote server
   * accepted the request before the failure.
   */
  fun onFailure(call: Call, e: IOException)

  /**
   * Called when the HTTP response was successfully returned by the remote server. The callback may
   * proceed to read the response body with [Response.body]. The response is still live until its
   * response body is [closed][ResponseBody]. The recipient of the callback may consume the response
   * body on another thread.
   *
   * Note that transport-layer success (receiving a HTTP response code, headers and body) does not
   * necessarily indicate application-layer success: `response` may still indicate an unhappy HTTP
   * response code like 404 or 500.
   */
  @Throws(IOException::class)
  fun onResponse(call: Call, response: Response)
}

分別用於處理失敗和成功兩種情況的回調。

在enqueue的方法體內,首先通過一個synchronized關鍵字,確保這個方法只會執行一次,

然後通過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.get().forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host())
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

繼續看看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 // 最大請求數爲64
        if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // 相同的Host最大同時請求數爲5

        i.remove()
        asyncCall.callsPerHost().incrementAndGet()
        executableCalls.add(asyncCall)  // 把請求加入到list中
        runningAsyncCalls.add(asyncCall)
      }
      isRunning = runningCallsCount() > 0
    }

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

    return isRunning
  }

首先判斷是不是超過了最大請求數或者是相同Host的最大請求數,如果是的話就直接return

否則就執行asyncCall.excuteOn方法

    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)
        transmitter.noMoreExchanges(ioException)
        responseCallback.onFailure(this@RealCall, ioException)
      } finally {
        if (!success) {
          client.dispatcher.finished(this) // This call is no longer running!
        }
      }
    }

AsyncCall是RealCall裏面的一個內部類,因此在這裏已經持有了OkHttpClient對象,也就持有了Dispatcher,而這個executorService又是什麼呢?

其實這是一個線程池執行器,在Dispatcher中定義爲一個變量

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

而 executorService.execute(this) 中的 this 指的就是AsyncCall對象,一個實現了Runnable接口的對象

因此,上面的excuteOn方法,其實就是執行AsyncCall的run()方法啊。

   override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        transmitter.timeoutEnter()
        try {
          val response = getResponseWithInterceptorChain()  // 1
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)  //2 
        } catch (e: IOException) {
          if (signalledCallback) {
            // Do not signal the callback twice!
            Platform.get().log("Callback failure for ${toLoggableString()}", 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)
        }
      }
    }

這裏又用到了Kotlin的內聯函數,相當於在方法體外面多加了一層try…finally,關於內聯函數,大家可以看看官方文檔的說明:Kotlin中文網 - 內聯函數

inline fun threadName(name: String, block: () -> Unit) {
  val currentThread = Thread.currentThread()
  val oldName = currentThread.name
  currentThread.name = name
  try {
    block()
  } finally {
    currentThread.name = oldName
  }
}

首先我們來看註釋1

  @Throws(IOException::class)
  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, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)

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

這裏用到了另外一個設計模式:責任鏈模式,用來處理整個網絡請求中不同的部分,例如失敗重試,緩存,連接等等。這些不同的部分都通過攔截器的方式來實現。

chain.proceed(originalRequest)方法中,其實就是調用了RealInterceptorChain.proceed()方法

這個方法的源碼:

 @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.
    check(this.exchange == null || this.exchange.connection()!!.supportsUrl(request.url)) {
      "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().
    check(this.exchange == null || calls <= 1) {
      "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().
    check(exchange == null || index + 1 >= interceptors.size || next.calls == 1) {
      "network interceptor $interceptor must call proceed() exactly once"
    }

    check(response.body != null) { "interceptor $interceptor returned a response with no body" }

    return response
  }

可以看到,通過index + 1的方法,我們取出下一個攔截器,然後執行裏面的intercept方法,這就是proceed方法主要進行的工作。最終把Response返回。得到的這個Response又通過Callback.onResponse回調方法,使得我們可以獲取到這個Response對象。而如果中途拋出異常,那麼則會回調onFailure方法。

至於我們在上面加入的那些攔截器,詳細的說明會放到下一篇文章中講解,我們也會加入自定義攔截器的例子。畢竟這種情況在實際開發中還是經常會遇到的,例如自定義ConverterFactory來解析後臺返回的數據。

總結

最後,讓我們再來回顧一下OkHttp是如何發起請求的:

  • 構造一個OkHttpClient對象,這裏有許多的變量用於控制請求時候的參數
  • 構造一個Request對象,這裏主要是4個要素:url, method, header, body
  • 調用OkHttpClient.newCall(request).enqueue()方法發起請求,在RealCall類中,實現了Call接口,並且持有OkHttpClient和Request對象,還有一個AsyncCall的內部類,這個內部類就是用來發起異步請求的,這個類同時也實現了Runnable接口,它的getResponseWithInterceptorChain()方法通過責任鏈的模式,把請求相關的攔截器一個個加入到List中,然後再通過RealInterceptorChain的proceed()方法來執行這些不同的攔截器所定義的方法。
  • 最後成功則回調Callback.onResponse, 失敗回調Callback.onFailure

還有,到目前爲止我們已經發現了OkHttp使用了兩個設計模式,分別是:

  • Builder模式
  • 責任鏈模式

有興趣的童鞋可以自行上網查找相關資料。

好了,一個簡單的請求大致上就是這麼個流程,下一篇文章我們繼續深入瞭解OkHttp裏面的攔截器~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章