OkHttp execute()同步執(zhí)行流程分析(4.9.3版本)

目的:主要是分析使用execute方法獲取response

測試類,建議放到idea里面,對照查看分析。

/**
 * implementation("com.squareup.okhttp3:okhttp:4.9.3")
 * implementation("com.squareup.okhttp3:logging-interceptor:4.9.3")
 * */
object MainApp {

    private val TAG = javaClass.simpleName

    private val okHttpClient = createClient()

    @JvmStatic
    fun test() {
        val request = Request.Builder().get().url("http://www.baidu.com").build()

        try {
            /**1.開始執(zhí)行請求[RealCall.execute]==>
             * 2.獲取一個請求攔截器鏈,執(zhí)行各個攔截器赏半,最后返回response[RealCall.getResponseWithInterceptorChain]。
             * */
            val call = okHttpClient.newCall(request)
            //call.timeout().timeout(30,TimeUnit.MILLISECONDS)//設置單個請求的超時時間淆两。具體實現(xiàn)查看[AsyncTimeout]
            val resp = call.execute()
            Log.d(TAG, "resp==" + resp.body?.string())
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    @JvmStatic
    private fun createClient(): OkHttpClient {
        return OkHttpClient.Builder()

            /**
             * 整個調用過程的超時時間:解析DNS断箫、連接、寫入請求體琼腔、服務器處理和讀取響應體瑰枫。
             * 如果調用需要重定向或重試,所有這些都必須在一個超時時間內完成丹莲。
             * 整個請求開始到結束的時間光坝,如果超時,會調用cancel方法甥材,會拋出InterruptedIOException("timeout")異常盯另。
             * 單個請求設置方法 call.timeout().timeout(30,TimeUnit.MILLISECONDS)
             * 如果是異步請求,則是call的run執(zhí)行開始計時洲赵。
             * */
            .callTimeout(20, TimeUnit.SECONDS)
            //連接超時鸳惯,socket的連接時間商蕴,攔截器里面可以設置
            .connectTimeout(10, TimeUnit.SECONDS)
            //讀取超時,socket讀取數(shù)據(jù)超時時間芝发,攔截器里面可以設置
            .readTimeout(10, TimeUnit.SECONDS)
            //寫入超時绪商,socket發(fā)送數(shù)據(jù)超時時間,攔截器里面可以設置
            .writeTimeout(10, TimeUnit.SECONDS)
            //攔截器辅鲸,在攔截器最開始就會被調用
            .addInterceptor(Interceptor { chain ->
                Log.d(TAG, "Interceptor  AA1 ")
                return@Interceptor chain.proceed(chain.request())
            })
            .addInterceptor(Interceptor { chain ->
                Log.d(TAG, "Interceptor  AA2 ")
                return@Interceptor chain.proceed(chain.request())
            })
            //執(zhí)行請求的攔截器格郁,在請求的http,socket連接之后才會調用独悴。
            .addNetworkInterceptor(Interceptor { chain ->
                Log.d(TAG, "NET Interceptor  BB1 ")
                return@Interceptor chain.proceed(chain.request())
            })
            //打印log
            .addInterceptor(HttpLoggingInterceptor {
                Log.d("HTTPLOG", it)
            }.setLevel(HttpLoggingInterceptor.Level.BODY))
            //請求調度和執(zhí)行例书。異步調用的線程池管理,記錄所有的請求(同步異步的都記錄)刻炒。
            // 調用cancelAll决采,可以取消當前所有請求。
            .dispatcher(Dispatcher())
            .build()
    }

}

執(zhí)行Call的execute方法坟奥,最后是RealCall.getResponseWithInterceptorChain方法獲取response树瞭,主要看這個方法就好。

execute方法

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

    //okhttp.builder.callTimeout筏勒,從這里開始計時移迫。
    //如果超時時間內,還沒有返回response管行,就會返回timeout厨埋。
    //如果一個請求,整個流程要在10s內結束荡陷,不管連接、讀寫時間唉地,
    //反正10s內要結束朱盐,就可以設置這個參數(shù)骇径。
    timeout.enter()
    
    callStart()
    try {
      client.dispatcher.executed(this)
      return getResponseWithInterceptorChain()
    } finally {
      client.dispatcher.finished(this)
    }
  }

1.[RealCall.getResponseWithInterceptorChain]

/** 1.[RealCall.getResponseWithInterceptorChain] */
 @Throws(IOException::class)
  internal fun getResponseWithInterceptorChain(): Response {
    // Build a full stack of interceptors.
    val interceptors = mutableListOf<Interceptor>()
    interceptors += client.interceptors//OkHttp創(chuàng)建時添加的攔截器[OkHttpClient.Builder.addInterceptor]
    interceptors += RetryAndFollowUpInterceptor(client)//重試钱烟,重定向等
    interceptors += BridgeInterceptor(client.cookieJar)//添加部分請求頭數(shù)據(jù),必要的請求頭
    interceptors += CacheInterceptor(client.cache)//緩存處理
    interceptors += ConnectInterceptor//http連接迄沫,主要是socket連接,給出接收和發(fā)送數(shù)據(jù)的操作封裝Exchange,供后面的攔截器使用
    if (!forWebSocket) {
      interceptors += client.networkInterceptors//[OkHttpClient.Builder.addNetworkInterceptor]
    }
    interceptors += CallServerInterceptor(forWebSocket)//發(fā)送睬捶、接收和解析數(shù)據(jù),得到服務器Response。
    
    //組裝執(zhí)行鏈,注意里面的參數(shù)和什么時候執(zhí)行翔烁,什么時候創(chuàng)建白华。
    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,//攔截器
        index = 0,//執(zhí)行攔截器的位置
        exchange = null,//執(zhí)行到ConnectInterceptor時,才會創(chuàng)建贩耐。
        request = originalRequest,//請求
        connectTimeoutMillis = client.connectTimeoutMillis,//默認為[OkHttpClient.Builder.connectTimeout]
        readTimeoutMillis = client.readTimeoutMillis,//默認為[OkHttpClient.Builder.readTimeout]
        writeTimeoutMillis = client.writeTimeoutMillis//默認為[OkHttpClient.Builder.writeTimeout]
    )

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

2.[RealInterceptorChain.proceed]

/**2.[RealInterceptorChain.proceed]*/
 @Throws(IOException::class)
override fun proceed(request: Request): Response {
  check(index < interceptors.size)

  calls++

  ```

  // Call the next interceptor in the chain.
  val next = copy(index = index + 1, request = request)
  val interceptor = interceptors[index]

  /**
  * 在執(zhí)行完一個interceptor之后弧腥,會使用chain.proceed(chain.request()),
  * 這個chain是第一個RealInterceptorChain copy的(新的chain潮太,只是列表數(shù)據(jù)是初始的數(shù)據(jù))管搪,
  * chain的參數(shù)每次改變都是新copy一個RealInterceptorChain。
  */
  @Suppress("USELESS_ELVIS")
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")
  
  ```

  return response
}

后面只要弄清楚各個Interceptor的功能就行了铡买。

3.[RetryAndFollowUpInterceptor]

主要用于重定向或其他錯誤處理更鲁。
1.具體處理的錯誤碼看[RetryAndFollowUpInterceptor.followUpRequest]方法:

//找出響應接收[userResponse]的HTTP請求。
//這將添加身份驗證頭奇钞、遵循重定向或處理客戶端請求超時澡为。
//如果后續(xù)操作是不必要的或不適用的,則返回null景埃。
 @Throws(IOException::class)
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
  val route = exchange?.connection?.route()
  val responseCode = userResponse.code

  val method = userResponse.request.method
  when (responseCode) {
    HTTP_PROXY_AUTH -> {
      val selectedProxy = route!!.proxy
      if (selectedProxy.type() != Proxy.Type.HTTP) {
        throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
      }
      return client.proxyAuthenticator.authenticate(route, userResponse)
    }

    HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)

    HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
      return buildRedirectRequest(userResponse, method)
    }

    HTTP_CLIENT_TIMEOUT -> {
      // 408's are rare in practice, but some servers like HAProxy use this response code. The
      // spec says that we may repeat the request without modifications. Modern browsers also
      // repeat the request (even non-idempotent ones.)
      if (!client.retryOnConnectionFailure) {
        // The application layer has directed us not to retry the request.
        return null
      }

      val requestBody = userResponse.request.body
      if (requestBody != null && requestBody.isOneShot()) {
        return null
      }
      val priorResponse = userResponse.priorResponse
      if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
        // We attempted to retry and got another timeout. Give up.
        return null
      }

      if (retryAfter(userResponse, 0) > 0) {
        return null
      }

      return userResponse.request
    }

    HTTP_UNAVAILABLE -> {
   ...
      return null
    }

    HTTP_MISDIRECTED_REQUEST -> {
      ...
      return userResponse.request
    }

    else -> return null
  }
}

2.重定向請求次數(shù)

 /**
   *Chrome遵循21個重定向;Firefox,curl和wget緊隨20;Safari是16;HTTP/1.0推薦5媒至。
   */
  private const val MAX_FOLLOW_UPS = 20

4.[BridgeInterceptor]

這個里面主要是添加必要的請求頭信息,
比如:"Content-Type"谷徙,"Content-Length"拒啰,"Cookie","User-Agent"等完慧。
還有gzip相關處理谋旦。

5.[CacheInterceptor]

一句話,就是緩存處理骗随。
Serves requests from the cache and writes responses to the cache.
服務來自緩存的請求蛤织,并將響應寫入緩存。

6.[ConnectInterceptor]

會創(chuàng)建socket和加入連接池等鸿染,
okHttpClient配置[connectTimeout][readTimeout][writeTimeout]相關參數(shù)在這里起作用指蚜。
找到或創(chuàng)建一條數(shù)據(jù)交換通道(socket),返回設置Exchange用于操作request和Response,交給下一個攔截器處理涨椒。

@Throws(IOException::class)
  override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    
    /**initExchange
    ==>[ExchangeFinder.find]
    ==>[ExchangeFinder.findHealthyConnection]
    ==>[ExchangeFinder.findConnection]
    ==>[RealConnection.connect(
          connectTimeout,//超時相關參數(shù)
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )]*/
    val exchange = realChain.call.initExchange(chain)
    //設置exchange,交給下一個攔截器處理
    val connectedChain = realChain.copy(exchange = exchange)
    return connectedChain.proceed(realChain.request)
  }

7.client.networkInterceptors

socket連接之后摊鸡,就是OkHttpClient創(chuàng)建時addNetworkInterceptor里面的攔截器相關的處理了。

8.CallServerInterceptor

This is the last interceptor in the chain. It makes a network call to the server.
這就是最后一個執(zhí)行的攔截器蚕冬,注意是最后才開始執(zhí)行免猾,也是最先執(zhí)行完的一個攔截器。
使用exchange發(fā)送請求相關數(shù)據(jù)和接收解析返回的數(shù)據(jù)囤热。
可以理解為里面使用socket猎提,發(fā)送HTTP協(xié)議相關的數(shù)據(jù)和接收解析數(shù)據(jù)。
最后返回response旁蔼。
這里是得到了后臺數(shù)據(jù)給的response锨苏,并不代表最后execute就是返回這個response數(shù)據(jù)疙教。
比如RetryAndFollowUpInterceptor還要對response做解析處理,log攔截器會打印log等伞租,所有攔截器執(zhí)行完成贞谓,最后得到的response,才算整個請求完成葵诈。

總結

整個execute流程分析完成裸弦,至于里面的其他細節(jié),不做具體分析了作喘。
知道okhttp創(chuàng)建時理疙,各個參數(shù)的含義,攔截器執(zhí)行的順序和各自作用泞坦,
那么在使用時沪斟,就不會有太多迷茫的位置了。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末暇矫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子择吊,更是在濱河造成了極大的恐慌李根,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件几睛,死亡現(xiàn)場離奇詭異房轿,居然都是意外死亡,警方通過查閱死者的電腦和手機所森,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門囱持,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人焕济,你說我怎么就攤上這事纷妆。” “怎么了晴弃?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵掩幢,是天一觀的道長。 經(jīng)常有香客問我上鞠,道長际邻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任芍阎,我火速辦了婚禮世曾,結果婚禮上,老公的妹妹穿的比我還像新娘谴咸。我一直安慰自己轮听,他們只是感情好骗露,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蕊程,像睡著了一般椒袍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上藻茂,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天驹暑,我揣著相機與錄音,去河邊找鬼辨赐。 笑死优俘,一個胖子當著我的面吹牛,可吹牛的內容都是我干的掀序。 我是一名探鬼主播帆焕,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼不恭!你這毒婦竟也來了叶雹?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤换吧,失蹤者是張志新(化名)和其女友劉穎折晦,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沾瓦,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡满着,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了贯莺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片风喇。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖缕探,靈堂內的尸體忽然破棺而出魂莫,到底是詐尸還是另有隱情,我是刑警寧澤爹耗,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布豁鲤,位于F島的核電站,受9級特大地震影響鲸沮,放射性物質發(fā)生泄漏琳骡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一讼溺、第九天 我趴在偏房一處隱蔽的房頂上張望楣号。 院中可真熱鬧,春花似錦、人聲如沸炫狱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽视译。三九已至嬉荆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酷含,已是汗流浹背鄙早。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留椅亚,地道東北人限番。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像呀舔,于是被迫代替她去往敵國和親弥虐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容