简洁易懂的 OkHttp 请求的流程源码分析

本文为 OkHttp 的第一篇文章,主要是对整个请求的流程的梳理,对 OkHttp 整体有一个感性的认识。

本文基于 OkHttp 最新的 4.8.1版本进行源分析的,源码是 Kotlin 写的,做好准备。

依赖

implementation 'com.squareup.okhttp3:okhttp:4.8.1'

整体流程

先写一个 Demo 作为源码分析的入口,首先分析异步请求的整体流程,然后再看同步请求的整体流程,从整体上把握 OkHttp 的请求流程

1.OkHttp 的基本使用

class OkHttpActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //1 请求的 Client
        val okHttpClient = OkHttpClient()
        //2 构造出一个 Request 对象
        val request = Request.Builder()
            //API 接口由 wanandroid.com 提供
            .url("https://wanandroid.com/wxarticle/chapters/json")
            .get()
            .build()
        //3 创建出一个执行的 Call 对象
        val call = okHttpClient.newCall(request)
        //4 异步执行请求
        call.enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                e.printStackTrace()
            }

            override fun onResponse(call: Call, response: Response) {
                "response.code = ${response.code}".log()
            }
        })
    }
}

以上就是 OkHttp 的最基本的用法,接口请求的是鸿洋提供的玩Android 开放API

注释 1:实例化一个OkHttpClient对象,用来配置请求的 interceptors(插值器)读写超时时间等配置,其实通过内部类 Builder 构建的,内部会初始化一个 Dispatcher 对象,下面会用到。

//OkHttpClient.kt
constructor() : this(Builder())
// OkHttpClient内部的Builder类,通过
class Builder constructor() {
  //调度器
    internal var dispatcher: Dispatcher = Dispatcher()
  //拦截器 list
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
  //网络拦截器 list
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    internal var retryOnConnectionFailure = true
    internal var connectTimeout = 10_000
    internal var readTimeout = 10_000
    internal var writeTimeout = 10_000
}

注释 2:构建一个请求的对象

注释 3:通过OkHttpClient 创建出一个执行的 Call 对象,其实是一个 RealCall,这个是真正进行请求的

注释 4:调用 call 的 enqueue方法,执行请求。

Call 有两种请求方式: enqueue异步请求方法和 execute同步方法

先看 enqueue异步请求的全过程

execute同步方法的过程,会在后面具体分析

2.源码分析

我们分析的入口就是 call 的 enqueue方法

//Call.kt
interface Call : Cloneable {
    ...
  fun enqueue(responseCallback: Callback)
    ...
}

是一个接口,所以我们要看具体的 Call 对象是谁,okHttpClient.newCall(request)方法返回了一个 Call 对象

看一下这个方法

3.OkHttpClient 的 newCall 方法

//OkHttpClient.kt
/** Prepares the [request] to be executed at some point in the future. */
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

是一个 RealCall 对象,好的看一下 RealCall 的 enqueue方法

4.RealCall 的 enqueue 方法

//RealCall.kt
override fun enqueue(responseCallback: Callback) {
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  //1
  callStart()
  //2
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

注释 1:调用的是 EventListener 的 callStart方法,是一个全局监听请求过程的方法,不是重点

注释 2:调用 dispatcher 的 enqueue方法,传进去了 AsyncCall 对象和 responseCallback参数

AsyncCall下面会具体看,responseCallback就是我们代码写的 CallBack,用来回调请求结果的。

5.Dispatcher 的 enqueue 方法

//Dispatcher.kt

/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()

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

    if (!call.call.forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  //2
  promoteAndExecute()
}

注释 1:把 AsyncCall 对象添加到 readyAsyncCalls 这个 list 中,这个集合下面会用到

注释 2:promoteAndExecute 推举并执行,这个方法是重点。

6.Dispatcher 的 promoteAndExecute 方法

//Dispatcher.kt
private fun promoteAndExecute(): Boolean {
  this.assertThreadDoesntHoldLock()
    //1
  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    //2
    val i = readyAsyncCalls.iterator()
    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()
      //3
      executableCalls.add(asyncCall)
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }
    //4
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    //5
    asyncCall.executeOn(executorService)
  }

  return isRunning
}

注释 1:声明一个 AsyncCall 的集合

注释 2:遍历readyAsyncCalls这个集合

注释 3:取出每一个AsyncCall存到 executableCalls 这个集合中

注释 4:遍历 executableCalls 这个集合

注释 5:调用 asyncCall 的 executeOn 方法传入了参数executorService,这个是重点

其中executorService的为ExecutorService是一个线程池,用来执行后台任务的

//Dispatcher.kt
@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!!
  }

7.AsyncCall 的 executeOn 方法

//AsyncCall.kt
fun executeOn(executorService: ExecutorService) {
  client.dispatcher.assertThreadDoesntHoldLock()

  var success = false
  try {
    //1
    executorService.execute(this)
    success = true
  } catch (e: RejectedExecutionException) {
    val ioException = InterruptedIOException("executor rejected")
    ioException.initCause(e)
    noMoreExchanges(ioException)
    //2
    responseCallback.onFailure(this@RealCall, ioException)
  } finally {
    if (!success) {
      client.dispatcher.finished(this) // This call is no longer running!
    }
  }
}

注释 1:把 AsyncCall 放到后台执行,那AsyncCall肯定一个Runnable 了,一会儿去看下它的 run方法做了什么

注释 2:线程池任务满了,拒绝执行,抛异常直接回调失败。

8.AsyncCall 的 run 方法

//RealCall.kt 的内部类
//AsyncCall.kt
internal inner class AsyncCall(
    private val responseCallback: Callback
  ) : Runnable {
    
    override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        var signalledCallback = false
        timeout.enter()
        try {
          //1
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          //2
          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 {
            //3
            responseCallback.onFailure(this@RealCall, e)
          }
        } catch (t: Throwable) {
          cancel()
          if (!signalledCallback) {
            val canceledException = IOException("canceled due to $t")
            canceledException.addSuppressed(t)
            //4
            responseCallback.onFailure(this@RealCall, canceledException)
          }
          throw t
        } finally {
          client.dispatcher.finished(this)
        }
      }
    }
  }

}

注释 1:getResponseWithInterceptorChain()方法直接返回了一个response对象

注释 2:把 response 通过 responseCallback 接口回调出去

注释 3:发生异常,回调失败接口

注释 4:发生异常,回调失败接口

到现在 Call 的异步请求全过程就看完了,而最关键的response是怎么得到的也就是 getResponseWithInterceptorChain()方法,会在下一篇文章分析。这篇只涉及到请求的流程

9.Call 的 execute 方法

Call 总共有两种请求方式一个 enqueue还有一个 execute方法,接下来看下同步执行的方法

我们上面可以得知 Call 其实是 RealCall,所以直接看 RealCall 的 execute 方法

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

  timeout.enter()
  callStart()
  try {
    client.dispatcher.executed(this)
    //1 拿到 response 直接返回
    return getResponseWithInterceptorChain()
  } finally {
    client.dispatcher.finished(this)
  }
}

这个 execute方法是运行在主线程的,直接通过 getResponseWithInterceptorChain方法获取 response 返回

10.总结

10.1.OkHttp 是如何进行异步请求的?

  • 通过 OkHttpClient 和 Request 参数构造出 RealCall 对象
  • 通过 RealCall 对象的 enqueue方法进行网络请求
  • 底层调用了 Dispatcher 的 enqueue方法,并把 AsyncCall 对象实例化,把 Callback 参数传入 AsyncCall 对象中
  • 通过 promoteAndExecute 方法把集合中 AsyncCall 遍历出来,挨个执行 AsyncCall 的 executeOn方法,并把创建的线程池,作为参数传递进去
  • 调用线程池的 execute方法,执行 AsyncCall 的 run 方法
  • 在 AsyncCall 的 run 方法中,通过 getResponseWithInterceptorChain方法获取 response,并通过Callback的 onResponse 返回,如果出现异常则调用 onFailure方法。

10.2.OkHttp 是如何进行同步请求的?

  • 通过 OkHttpClient 和 Request 参数构造出 RealCall 对象
  • 通过 RealCall 对象的 execute方法进行网络请求
  • 通过 getResponseWithInterceptorChain方法获取 response,直接返回

11.源码地址

OkHttpActivity.kt

12.原文地址

OkHttp请求的流程的梳理

13.参考资料

OkHttp

推荐一下我开源的项目 WanAndroid 客户端

WanAndroidJetpack 架构图

wanandroid-arch.jpg
  • 一个纯 Android 学习项目,WanAndroid 客户端。
  • 项目采用 MVVM 架构,用 Kotlin 语音编写。
  • Android Jetpack 的大量使用包括但不限于LifecycleLiveDataViewModelDatabindingRoomConstraintLayout等,未来可能会更多。
  • 采用 RetrofitKotlin-Coroutine 协程进行网络交互。
  • 加载图片 Glide 主流加载图片框架。
  • 数据存储主要用到了 Room 和腾讯的 MMKV

Kotlin + MVVM + Jetpack + Retrofit + Glide 的综合运用,是学习 MMVM 架构的不错的项目。

此项目本身也是一个专门学习 Android 相关知识的 APP,欢迎下载体验!

源码地址(附带下载链接)

WanAndroidJetpack

APP 整体概览

wan-gif.gif

喜欢的点个 Stars,有问题的请提 Issues。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容