小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。
OkHttp源码剖析(四) 报文读写工具ExchangeCodec
Dispatcher
OkHttp 对异步请求的线程池主要依赖Dispatcher来实现。
Dispatcher是用于调度后台发起的网络请求的调度器, 有后台总请求数和单主机总请求数的控制。
每个Dispatcher调度器都有一个 ExecutorService, 通过java excutor实现,在内部运行调用,用于真正执行call。
分析一下Dispatcher的几个参数:
maxRequests最大请求数:64maxRequestsPerHost每个 host 最大请求:5readyAsyncCalls双向的等待队列runningAsyncCalls双向的执行队列promoteAndExecute()将合格的 readyAsyncCalls 升级到 runningAsyncCalls
OkHttp 任务调度器的设计是将请求分别放到了两个队列中,分别是等待队列及执行队列(分同异步),执行队列又分为了同步的执行队列和异步执行队列。
/** Ready async calls in the order they'll be run. */
private val readyAsyncCalls = ArrayDeque<AsyncCall>()
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private val runningAsyncCalls = ArrayDeque<AsyncCall>()
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private val runningSyncCalls = ArrayDeque<RealCall>()
先对调度器有个大体认识:OkHttp 的任务调度器Dispatcher,在同步请求来时,将其加入到同步执行队列;在异步请求来时,将其加入异步等待队列后遍历异步等待队列尝试执行异步等待任务。每个请求完成时,通知到 Dispatcher,Dispatcher 再次遍历准备队列尝试执行任务,若没有执行则说明等待队列是空的,此时会调用 idleCallback.run 执行一些空闲时的任务。
同/异步请求下的dispatcher
OkHttp 的执行有两种方式,enqueue 及 execute,分别代表了异步请求与同步请求。但是无论哪种请求,虽然看上去是call在进行,但实际上都是调度器Dispatcher在进行同/异步操作。
execute
override fun execute(): Response {
check(executed.compareAndSet(false, true)) { "Already Executed" }
timeout.enter()
callStart()
try {
client.dispatcher.executed(this)
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
同步方法execute()实际调用的是调度器的 Dispatcher.executed 方法,通知 Dispatcher 要执行该Call ,之后调用getResponseWithInterceptorChain方法获取 Response,不论是否成功都会调用Dispatcher.finished 通知 Dispatcher 该 Call 执行完成。
client.dispatcher.executed(this)方法直接将同步的请求放进了同步执行队列runningSyncCalls当中。
/** Used by [Call.execute] to signal it is in-flight. */
@Synchronized internal fun executed(call: RealCall) {
runningSyncCalls.add(call)
}
enqueue
如下图所示,异步方法execute()实际调用的是调度器的 Dispatcher.executed 方法。
在用户使用client.newCall(request).enqueue(new Callback())方法后,调用到RealCall的enqueue()方法,enqueue()方法如下:
override fun enqueue(responseCallback: Callback) {
check(executed.compareAndSet(false, true)) { "Already Executed" }
callStart()
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
RealCall.enqueue()中调用了dispacher调度器中的enqueue()方法。
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 加入等待队列
readyAsyncCalls.add(call)
if (!call.call.forWebSocket) {
// 寻找同一个host的Call
val existingCall = findExistingCallWithHost(call.host)
// 复用Call的callsPerHost
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
promoteAndExecute()
}
dispacher.enqueue()方法先将call加入到等待队列 readAsyncCalls 中,再调用 findExistingCallWithHost 方法尝试寻找 host 相同的 Call,它的寻找会遍历 readyAsyncCalls 及 runningAsyncCalls 两个队列。
若找到了对应的 Call,则会调用 call.reuseCallsPerHostFrom 来复用这个 Call 的 callsPerHost,从而便于统计一个 host 对应的 Call 的个数,它是一个 AtomicInteger。
之后调用 dispacher.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 // Max capacity.
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
i.remove()
asyncCall.callsPerHost.incrementAndGet()
executableCalls.add(asyncCall)
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
asyncCall.executeOn(executorService)
}
return isRunning
}
promoteAndExecute() 方法遍历了 readyAsyncCalls 队列去寻找能够执行的 AsynCall,若找到,会在最后统一调用 AsyncCall.executeOn 方法,在自己的 executorService 线程池中执行该 Call。其中,执行中的任务不能超过 maxRequests。
如下代码所示,executorService由Dispatcher.executorService()方法创建,并将创建的线程池返回。
@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!!
}
有了线程池,使得asyncCall可以在该线程池所提供的线程中发起 HTTP 请求,获取 Response 并回调 Callback 的对应方法,从而实现任务的调度。 executorService.execute(this)这一行,利用了调度器中的ExecutorService对AsyncCall进行了execute操作。
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
// 在对应的ExecutorService中执行该AsyncCall
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
// 出现问题,调用Callback对应方法
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
// 通知Dispatcher请求完成
if (!success) {
client.dispatcher.finished(this) // This call is no longer running!
}
}
}
如下代码所示,可以看出,AsyncCall 是一个 Runnable, executorService.execute(this)利用了调度器中的ExecutorService对AsyncCall进行了execute操作,实际上是执行了AsyncCall这个 Runnable实现的 run 方法,方法中最终调用到了最关键最核心的getResponseWithInterceptorChain()方法。
override fun run() {threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
timeout.enter()
try {
//// 获取Response
val response = getResponseWithInterceptorChain()
signalledCallback = true
responseCallback.onResponse(this@RealCall, response)
} catch (e: Exception) {
...
}finally {
// 通知Dispatcher请求完成
client.dispatcher.finished(this)
}
}
}
请求完后,不论成功失败,都会调用到 finished() 方法通知 Dispatcher 请求结束:
/** Used by [AsyncCall.run] to signal completion. */
internal fun finished(call: AsyncCall) {
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
这里, finished() 方法调用到了 finished 的另一个重载函数:
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
// 尝试执行等待队列中的任务
val isRunning = promoteAndExecute()
if (!isRunning && idleCallback != null) {
idleCallback.run()
}
}
finished 再次调用了 promoteAndExecute 方法尝试执行等待队列中的任务,若当前等待队列中没有需要执行的任务,说明目前还比较空闲,没有到达设定的 maxRequests 。此时会调用 idleCallback.run 执行一些空闲 Callback。