okhttp 請(qǐng)求過程源碼分析

本篇基于 okhttp 4.9.3 進(jìn)行分析學(xué)習(xí)邀窃。

okhttp 項(xiàng)目地址

前言

Android 項(xiàng)目開發(fā)中離不開網(wǎng)絡(luò)請(qǐng)求,說到網(wǎng)絡(luò)請(qǐng)求一定會(huì)想到 okhttp。

okhttp 是如何完成一次請(qǐng)求的呢,接下來我們一步步來分析。

首先看下完整的流程圖,此處直接使用 Piasy 大佬的圖

一、如何發(fā)起請(qǐng)求

1. Android 工程中使用配置

添加網(wǎng)絡(luò)請(qǐng)求權(quán)限 <uses-permission android:name="android.permission.INTERNET"/>

添加依賴 implementation "com.squareup.okhttp3:okhttp:4.9.3"

2. 發(fā)起請(qǐng)求

以發(fā)送 GET 請(qǐng)求為例医咨,其他類型請(qǐng)求也是通過在創(chuàng)建 requst 對(duì)象時(shí)進(jìn)行設(shè)置。

// 創(chuàng)建 OkHttpClient 實(shí)例對(duì)象架诞,設(shè)置了超時(shí)時(shí)間
val client = OkHttpClient.Builder()
    .callTimeout(10, TimeUnit.SECONDS)
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(10, TimeUnit.SECONDS)
    .writeTimeout(10, TimeUnit.SECONDS)
    .build()

// 創(chuàng)建 requset 對(duì)象拟淮,設(shè)置 url、請(qǐng)求方式侈贷、header 等信息
val request = Request.Builder().url(url).build()

// 同步執(zhí)行惩歉,需放到子線程中執(zhí)行
GlobalScope.launch(Dispatchers.IO) {
    val response = client.newCall(request).execute().body?.string()
}

// 異步執(zhí)行
client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        Log.d(TAG, e.message ?: "have IOException")
    }

    override fun onResponse(call: Call, response: Response) {
        val response = response.body?.string()
        Log.d(TAG, "enqueue() response = $response")
    }
})


二等脂、發(fā)起一次請(qǐng)求都經(jīng)歷了什么

分析上面 GET 請(qǐng)求時(shí)用到的對(duì)象,源碼只保留流程中重要的部分撑蚌。

1. OkHttpClient

open class OkHttpClient internal constructor(
  builder: Builder
) : Cloneable, Call.Factory, WebSocket.Factory {

    ...
    
    // OkHttpClient 可以構(gòu)建 Call 的原因上遥,因?yàn)閷?shí)現(xiàn)了 Call.Factory
    override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

    ...

    // 默認(rèn)構(gòu)建參數(shù)
    class Builder constructor() {
      internal var dispatcher: Dispatcher = Dispatcher() // 調(diào)度器
      internal var connectionPool: ConnectionPool = ConnectionPool() // 連接池
      internal val interceptors: MutableList<Interceptor> = mutableListOf() // 攔截器
      internal val networkInterceptors: MutableList<Interceptor> = mutableListOf() // 網(wǎng)絡(luò)攔截器 
      internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
      internal var retryOnConnectionFailure = true // 連接失敗時(shí)重試
      internal var authenticator: Authenticator = Authenticator.NONE // 驗(yàn)證器
      internal var followRedirects = true // 允許重定向
      internal var followSslRedirects = true // 允許 https 同 http 之間的重定向
      internal var cookieJar: CookieJar = CookieJar.NO_COOKIES // cookie
      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
      internal var connectTimeout = 10_000
      internal var readTimeout = 10_000
      internal var writeTimeout = 10_000
      internal var pingInterval = 0
      internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
      internal var routeDatabase: RouteDatabase? = null
      
      // 傳入自己構(gòu)建的參數(shù)
      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
        this.minWebSocketMessageToCompress = okHttpClient.minWebSocketMessageToCompress
        this.routeDatabase = okHttpClient.routeDatabase
      }
      
      ...
}

使用建造者模式,用來設(shè)置攔截器争涌、超時(shí)時(shí)間粉楚、緩存、重定向等屬性亮垫。

2. Request

class Request internal constructor(
  @get:JvmName("url") val url: HttpUrl,
  @get:JvmName("method") val method: String,
  @get:JvmName("headers") val headers: Headers,
  @get:JvmName("body") val body: RequestBody?,
  internal val tags: Map<Class<*>, Any>
) {
    
    ...
    
    
    open class Builder {
      internal var url: HttpUrl? = null
      internal var method: String
      internal var headers: Headers.Builder
      internal var body: RequestBody? = null

      internal var tags: MutableMap<Class<*>, Any> = mutableMapOf()

      constructor() {
        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
      }
      
      ...
}

也是使用建造者模式模软,來設(shè)置請(qǐng)求方法、url饮潦、請(qǐng)求體燃异、header 信息。

3. Call

interface Call : Cloneable {
  // 返回 call 的 request 對(duì)象
  fun request(): Request

  // 執(zhí)行同步請(qǐng)求
  @Throws(IOException::class)
  fun execute(): Response

  // 執(zhí)行異步請(qǐng)求
  fun enqueue(responseCallback: Callback)
    
  // 取消
  fun cancel()
  
  // 是否已經(jīng)執(zhí)行請(qǐng)求
  fun isExecuted(): Boolean

  // 是否已經(jīng)取消
  fun isCanceled(): Boolean
  
  // 超時(shí)信息
  fun timeout(): Timeout

  // 克隆當(dāng)前 call继蜡,返回新的對(duì)象方便再次執(zhí)行請(qǐng)求回俐,同一個(gè) call 只能執(zhí)行一次
  public override fun clone(): Call

  fun interface Factory {
    fun newCall(request: Request): Call
  }
}

接口類核畴,定義了在一次請(qǐng)求中可能用到的方法寒亥。

4. RealCall

實(shí)現(xiàn) Call 接口,是最終的請(qǐng)求執(zhí)行者俺猿,下面按同步請(qǐng)求和異步請(qǐng)求分成兩部分來說碘举。

a忘瓦、同步請(qǐng)求部分
// RealCall.execute
override fun execute(): Response {
  // 每個(gè) call 只能請(qǐng)求一次
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  timeout.enter()
  callStart()
  try {
    // 將當(dāng)前 call 添加到 Dispatcher 類的 runningSyncCalls 列表中
    client.dispatcher.executed(this)
    // 經(jīng)過一系列攔截器拿到請(qǐng)求結(jié)果
    return getResponseWithInterceptorChain()
  } finally {
    // 將當(dāng)前 call 從 Dispatcher 類的 runningSyncCalls 列表中移除
    client.dispatcher.finished(this)
  }
}

// RealCall.getResponseWithInterceptorChain
@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
  // Build a full stack of interceptors.
  val interceptors = mutableListOf<Interceptor>()
  // 開始添加攔截器
  // 在 OkHttpClient 中設(shè)置的 攔截器
  interceptors += client.interceptors
  // 處理重試和重定向的攔截器
  interceptors += RetryAndFollowUpInterceptor(client)
  // 將應(yīng)用請(qǐng)求轉(zhuǎn)換為網(wǎng)絡(luò)請(qǐng)求,將請(qǐng)求結(jié)果轉(zhuǎn)換為用戶友好響應(yīng)的攔截器
  interceptors += BridgeInterceptor(client.cookieJar)
  // 讀取緩存引颈、更新緩存的攔截器
  interceptors += CacheInterceptor(client.cache)
  // 和服務(wù)器連接的攔截器
  interceptors += ConnectInterceptor
  if (!forWebSocket) {
    // 在 OkHttpClient 中設(shè)置的 networkInterceptors 
    interceptors += client.networkInterceptors
  }
  // 請(qǐng)求服務(wù)器耕皮、讀取響應(yīng)結(jié)果的攔截器
  interceptors += CallServerInterceptor(forWebSocket)

  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ǔn)秸{(diào)用攔截器中的處理
    val response = chain.proceed(originalRequest)
    if (isCanceled()) {
      response.closeQuietly()
      throw IOException("Canceled")
    }
    // 返回結(jié)果
    return response
  } catch (e: IOException) {
    calledNoMoreExchanges = true
    throw noMoreExchanges(e) as Throwable
  } finally {
    if (!calledNoMoreExchanges) {
      noMoreExchanges(null)
    }
  }
}

主要處理邏輯在 getResponseWithInterceptorChain() 該方法中,dispatcher 用來改變執(zhí)行狀態(tài)线欲。

b明场、異步請(qǐng)求部分
// RealCall.enqueue
override fun enqueue(responseCallback: Callback) {
  // 每個(gè) call 只能請(qǐng)求一次
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  callStart()
  // 將 AsyncCall 添加到 Dispatcher.readyAsyncCalls 中
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

接下來看 enqueue() 方法

// Dispatcher.enqueue
internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    // 添加到 readyAsyncCalls 中
    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()
}

接下來看 promoteAndExecute() 方法

// Dispatcher.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.
      // 移除自己
      i.remove()
      asyncCall.callsPerHost.incrementAndGet()
      // 添加到 executableCalls 中
      executableCalls.add(asyncCall)
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }
  // 遍歷 executableCalls 開始執(zhí)行
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService)
  }

  return isRunning
}

執(zhí)行的 run 方法

// RealCall#AsyncCall.run
override fun run() {
  threadName("OkHttp ${redactedUrl()}") {
    var signalledCallback = false
    timeout.enter()
    try {
      // 同步請(qǐng)求時(shí)分析過的方法
      val response = getResponseWithInterceptorChain()
      signalledCallback = true
      // 調(diào)用回調(diào)方法汽摹,拿到請(qǐng)求結(jié)果
      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)
    }
  }
}

看到這里再去看開頭的流程圖就很容易了

分析了請(qǐng)求的大致流程李丰,細(xì)節(jié)分析后續(xù)有待完善。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逼泣,一起剝皮案震驚了整個(gè)濱河市趴泌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拉庶,老刑警劉巖嗜憔,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異氏仗,居然都是意外死亡吉捶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呐舔,“玉大人币励,你說我怎么就攤上這事∩浩矗” “怎么了食呻?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)澎现。 經(jīng)常有香客問我仅胞,道長(zhǎng),這世上最難降的妖魔是什么剑辫? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任干旧,我火速辦了婚禮,結(jié)果婚禮上妹蔽,老公的妹妹穿的比我還像新娘莱革。我一直安慰自己,他們只是感情好讹开,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布盅视。 她就那樣靜靜地躺著,像睡著了一般旦万。 火紅的嫁衣襯著肌膚如雪闹击。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天成艘,我揣著相機(jī)與錄音赏半,去河邊找鬼。 笑死淆两,一個(gè)胖子當(dāng)著我的面吹牛断箫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播秋冰,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仲义,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了剑勾?” 一聲冷哼從身側(cè)響起埃撵,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎虽另,沒想到半個(gè)月后暂刘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捂刺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年谣拣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了募寨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡森缠,死狀恐怖绪商,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辅鲸,我是刑警寧澤格郁,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站独悴,受9級(jí)特大地震影響例书,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜刻炒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一决采、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坟奥,春花似錦树瞭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至访敌,卻和暖如春凉敲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寺旺。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工爷抓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阻塑。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓蓝撇,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親陈莽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渤昌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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