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。
参考
https://square.github.io/okhttp/
https://zhuanlan.zhihu.com/p/58093669
https://www.jianshu.com/p/8d69fd920166
https://juejin.cn/post/6844904037154816007