OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度

OkHttp 的由来

对于 HTTP,早期的安卓提供了两种 API:一种是 Java 原生的 URLConnection,另一种是 Apache 的 HttpClient。

这两种方案,Square 公司觉得都不好用,就对 API 进行了封装,形成了最初版本的 OkHttp 框架。之后 Square 先是移除了 HttpClient 内部实现,接着连 URLConnection 内部实现也移除了。两者都不用,选择自己实现底层支持,比如 TCP 连接的建立等工作。

接着,安卓官方也将其 URLConnection 的底层实现改为了 OkHttp 的底层实现,比如:如何通过 DNS 获取 IP 地址、通过 Socket 发送和接收 HTTP 数据等过程,都是使用的 OkHttp 的代码。

我们先来看看如何简单使用 OkHttp。

简单用法

来到 OkHttp 的主页,通过 OkHttp 来发送一个 GET 请求,并打印响应的状态码。

添加依赖

kotlin 复制代码
// Module-level: build.gradle.kts
dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.11.0") // 如果没有看到 OkHttp 的源码,可以换个版本
}

然后创建一个 Empty Views Activity 项目,其 MainActivity 中的代码如下所示:

kotlin 复制代码
import java.io.IOException
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

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

        // 安卓中一般是使用 enqueue() 方法
        // 因为 execute() 方法是同步的,enqueue() 方法是异步的
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {

            }

            override fun onResponse(call: Call, response: Response) {
                println("Status Code: ${response.code}")
            }
        })

    }
}

运行结果:

less 复制代码
I/System.out    Status Code: 200

源码

了解了其基本用法后,我们来跟踪源码,看看它究竟在背后干了什么。

先点开最直接的 enqueue() 方法:

kotlin 复制代码
// Call.kt
interface Call : Cloneable {
  fun enqueue(responseCallback: Callback)
}

但发现它是 Call 接口中的一个抽象方法,所以我们需要回头看看 newCall() 方法创建的对象是什么。

kotlin 复制代码
// OkHttpClient.kt
override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

发现它创建了一个 RealCall 对象。

kotlin 复制代码
// RealCall.kt
class RealCall(
  val client: OkHttpClient,
  val originalRequest: Request,
  val forWebSocket: Boolean
) : Call {...}

创建该对象需要传入三个参数:

  • client: 就是我们先前创建的 OkHttpClient 对象,所有通用配置都通过它来完成,比如网络的超时时长。

  • originalRequest: 是我们前面创建的 Request 请求对象,存放用来发起 HTTP 请求的基本信息,比如请求方法、请求路径、请求头等。

    另外参数名的意思是"原始请求",这是因为 OkHttp 会把该请求对象进行多次封装改变,得到的更完整的请求对象,才拿去用于网络请求。

  • forWebSocket: 这个参数一般用不到,默认为 false 就行。因为 WebSocket 协议的作用是让服务器能够主动发送消息给客户端,但这种需求我们一般用不到,它常用于需要频繁刷新数据的场景,比如股票交易、虚拟货币交易。

知道了创建的是 RealCall 对象后,我们来到 RealCall 中查找其 enqueue() 方法的实现:

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

  callStart()
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

这段代码的主要逻辑在于后两行,我们先看 callStart() 方法。

kotlin 复制代码
// RealCall.kt
private fun callStart() {
  this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
  eventListener.callStart(this)
}

第一行在跟踪程序错误,用于错误分析;第二行是调用了一个回调方法,eventListener 是一个监听器,用于监听与 HTTP 交互的过程,比如 TCP 连接的建立。

总的来说,callStart 方法就是一个辅助方法,可以不用管,对程序运行无实际影响。我们再回去看看 client.dispatcher.enqueue(AsyncCall(responseCallback)) 中的 dispatcherenqueue()AsyncCall()

先来看 dispatcher,发现它是一个 Dispatcher 对象。

kotlin 复制代码
// OkHttpClient.kt
@get:JvmName("dispatcher") val dispatcher: Dispatcher = builder.dispatcher

而 Dispatcher 是 OkHttp 用于线程调度的,当需要多个请求同时运行,就需要有多个线程,Dispatcher 就是管理多个线程的。OkHttp 使用 Dispatcher,是想要通过统一的线程池和请求队列来复用线程、管理并发,避免每次请求都需要创建新线程。

接着看 enqueue() 方法:

kotlin 复制代码
// Dispatcher.kt
internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    readyAsyncCalls.add(call)
    
    // 统计某个主机的请求数
    if (!call.call.forWebSocket) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  promoteAndExecute()
}

方法内部会将参数的 call 添加到双向队列中,存放已准备好要执行的请求。那么进入队列的请求会在什么时候被执行?其实就在 promoteAndExecute() 方法中。

我们进入 promoteAndExecute() 方法:

kotlin 复制代码
// Dispatcher.kt
// 将合适的 Call 取出,拿去执行
private fun promoteAndExecute(): Boolean {
  this.assertThreadDoesntHoldLock()

  val executableCalls = mutableListOf<AsyncCall>()
  val isRunning: Boolean
  synchronized(this) {
    // 遍历 readyAsyncCalls
    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.
      
      // 选择执行后,不会导致超出 maxRequests、maxRequestsPerHost 容量的 asyncCall
      i.remove()
      asyncCall.callsPerHost.incrementAndGet()
      // 放进 executableCalls 可执行的 Call 列表中
      executableCalls.add(asyncCall)
      // 放进 runningAsyncCalls 正在执行的 Call 队列中,用于记录
      runningAsyncCalls.add(asyncCall)
    }
    isRunning = runningCallsCount() > 0
  }
  // 执行上述的 executableCalls 列表
  for (i in 0 until executableCalls.size) {
    val asyncCall = executableCalls[i]
    asyncCall.executeOn(executorService) // 关键执行代码
  }

  return isRunning
}

所以,为了知道 enqueue() 方法的具体逻辑,我们来看看 executeOn() 方法的内部:

kotlin 复制代码
// RealCall.kt
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!
    }
  }
}

execute() 方法是 Executor 接口中的抽象方法,参数是 Runnable 类型,它会将参数放到后台去执行。

java 复制代码
// Executor.java
public interface Executor {
    void execute(Runnable command);
}

那我们来看看它实际执行的内容,来到 AsyncCallrun 方法。(注意:AsyncCallRealCall 的内部类,它实现了 Runnable 接口)。

kotlin 复制代码
// RealCall.kt
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)
      }
    }
  }
}

可以看到其中调用了 getResponseWithInterceptorChain() 方法获得了一个服务器返回的响应,那么我们很清楚,这就是这个方法的关键。之后它会将这个响应传给 onResponse 回调方法,而 responseCallback 就是我们在调用 enqueue 方法时传入的 Callback 实例,所以我们可以在 onResponse 方法中打印响应返回的状态码。

出错的话,会调用 responseCallbackonFailure 回调。

流程总结

我们来对 enqueue 的异步流程做个总结。其本质是经典的 "生产者-消费者"模型:

  1. 首先,生产者是 RealCall.enqueue,它将请求任务包装成 AsyncCall,然后放入 Dispatcher 的待处理队列 readyAsyncCalls 中。
  2. 调度中心是 Dispatcher.promoteAndExecute,它根据当前的并发限制,从待处理队列中取出合适的任务,然后放进运行中队列 runningAsyncCalls 进行状态跟踪,并最终通过 ExecutorService 线程池来处理。
  3. 消费者是 AsyncCall.run,执行网络请求,并通过 Callback 将结果返回。

理解了这个模型,整个异步调度的框架就很清晰了。

至此,OkHttp 执行一个异步请求的调度流程 框架,我们就已经看完了。接下来我们会进入请求的关键,也就是 getResponseWithInterceptorChain 方法的内部实现。不过在讲它之前,会先看看 OkHttpClient 中各个可配置的参数有哪些,让我们先对 OkHttp 有个大致了解。

相关推荐
whysqwhw1 小时前
安卓图片性能优化技巧
android
风往哪边走1 小时前
自定义底部筛选弹框
android
Yyyy4822 小时前
MyCAT基础概念
android
Android轮子哥2 小时前
尝试解决 Android 适配最后一公里
android
风往哪边走4 小时前
自定义仿日历组件弹框
android
没有了遇见4 小时前
Android 外接 U 盘开发实战:从权限到文件复制
android
Monkey-旭5 小时前
Android 文件存储机制全解析
android·文件存储·kolin
zhangphil6 小时前
Android Coil 3拦截器Interceptor计算单次请求耗时,Kotlin
android·kotlin
DokiDoki之父6 小时前
多线程—飞机大战排行榜功能(2.0版本)
android·java·开发语言