赞
踩
transmitter.timeoutEnter()
transmitter.callStart()
try {
client.dispatcher.executed(this)//第1步
return getResponseWithInterceptorChain()//第2步
} finally {
client.dispatcher.finished(this)//第3步
}
}
把大象装冰箱,统共也只需要三步。
调用Dispatcher的execute方法,那Dispatcher是什么呢?从名字来看它是一个调度器,调度什么呢?就是所有网络请求,也就是RealCall对象。网络请求支持同步执行和异步执行,异步执行就需要线程池、并发阈值这些东西,如果超过阈值需要将超过的部分存储起来,这样一分析Dispatcher的功能就可以总结如下:
- 记录同步任务、异步任务及等待执行的异步任务。
- 线程池管理异步任务。
- 发起/取消网络请求API:execute、enqueue、cancel。
OkHttp设置了默认的最大并发请求量 maxRequests = 64 和单个host支持的最大并发量 maxRequestsPerHost = 5。
同时用三个双端队列存储这些请求:
//异步任务等待队列
private val readyAsyncCalls = ArrayDeque()
//异步任务队列
private val runningAsyncCalls = ArrayDeque()
//同步任务队列
private val runningSyncCalls = ArrayDeque()
为什么要使用双端队列?很简单因为网络请求执行顺序跟排队一样,讲究先来后到,新来的请求放队尾,执行请求从对头部取。
说到这LinkedList表示不服,我们知道LinkedList同样也实现了Deque接口,内部是用链表实现的双端队列,那为什么不用LinkedList呢?
实际上这与readyAsyncCalls向runningAsyncCalls转换有关,当执行完一个请求或调用enqueue方法入队新的请求时,会对readyAsyncCalls进行一次遍历,将那些符合条件的等待请求转移到runningAsyncCalls队列中并交给线程池执行。尽管二者都能完成这项任务,但是由于链表的数据结构致使元素离散的分布在内存的各个位置,CPU缓存无法带来太多的便利,另外在垃圾回收时,使用数组结构的效率要优于链表。
回到主题,上述的核心逻辑在promoteAndExecute方法中:
#Dispatcher
private fun promoteAndExecute(): Boolean {
val executableCalls = mutableListOf()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
//遍历readyAsyncCalls
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.
//符合条件 从readyAsyncCalls列表中删除
i.remove()
//per host 计数加1
asyncCall.callsPerHost().incrementAndGet()
executableCalls.add(asyncCall)
//移入runningAsyncCalls列表
runningAsyncCalls.add(asyncCall)
}
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。