学习参考资料:OKHttp源码解析OKHttp源码分析——拦截器OKhttp完全解析-拦截器
一、概括
说到OKHttp请求的同步和异步,就要提到Dispatcher分发器了,根据前两篇的源码分析,可以知道在发起请求时,整个框架主要通过Call来封装每一次的请求。同时Call持有OkHttpClient和一份HttpEngine。而每一次的同步或者异步请求都会有Dispatcher的参与,不同的是:
同步
Dispatcher会在同步执行任务队列中记录当前被执行过得任务Call,同时在当前线程中去执行Call的getResponseWithInterceptorChain()方法,直接获取当前的返回数据Response;
异步
首先来说一下Dispatcher,Dispatcher内部实现了懒加载无边界限制的线程池方式,同时该线程池采用了 SynchronousQueue这种阻塞队列。SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等 待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用peek操作,因为只有移除元素时才有元素。显然这是一种快 速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中是最快处理任务的方式。对于高 频繁请求的场景,无疑是最适合的。
异步执行是通过Call.enqueue(Callback responseCallback)来执行,在Dispatcher中添加一个封装了Callback的Call的匿名内部类Runnable来执行当前 的Call。这里一定要注意的地方这个AsyncCall是Call的匿名内部类。AsyncCall的execute方法仍然会回调到Call的 getResponseWithInterceptorChain方法来完成请求,同时将返回数据或者状态通过Callback来完成。
二、源码分析
OkHttp的任务队列主要由两部分组成:
1. 任务分发器dispatcher:负责为任务找到合适的执行线程 2.网络请求任务线程池
readyCalls:待执行异步任务队列
runningCalls:运行中异步任务队列
executedCalls:运行中同步任务队列
executorService:任务队列线程池
ThreadPoolExecutor(PS:这里附上的代码为OKHttp2.5.0,后面有改动的源码,会附上3.7版本的代码,整体并不影响学习)
int corePoolSize: 最小并发线程数,这里并发同时包括空闲与活动的线程,如果是0的话,空闲一段时间后所有线程将全部被销毁
int maximumPoolSize: 最大线程数,当任务进来时可以扩充的线程最大值,当大于了这个值就会根据丢弃处理机制来处理
long keepAliveTime: 当线程数大于corePoolSize时,多余的空闲线程的最大存活时间,类似于HTTP中的Keep-alive
TimeUnit unit: 时间单位,一般用秒
BlockingQueue workQueue: 工作队列,先进先出,可以看出并不像Picasso那样设置优先队列
ThreadFactory threadFactory: 单个线程的工厂,可以打Log,设置Daemon(即当JVM退出时,线程自动结束)等
构建ThreadPoolExecutor可以看出,在Okhttp中,构建了一个阀值为[0, Integer.MAX_VALUE]的线程池,它不保留任何最小线程数,随时创建更多的线程数,当线程空闲时只能活60秒,它使用了一个不存储元素的阻塞工作队列,一个叫做"OkHttp Dispatcher"的线程工厂。
也就是说,在实际运行中,当收到10个并发请求时,线程池会创建十个线程,当工作完成后,线程池会在60s后相继关闭所有线程。
分发器执行图同步:
OkHttpClient client =newOkHttpClient();Requestrequest=newRequest.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();Responseresponse= client.newCall(request).execute();
其中最后的call.execute();我们来看一下同步中的execute()方法:
(PS:代码为3.7版本)
RealCall.execute重点为:
client.dispatcher().executed(this);
client.dispatcher().finished(this);
同步调用的执行逻辑是:1.将对应任务加入分发器 2.执行任务 3.执行完成后通知dispatcher对应任务已完成,对应任务出队
异步:
OkHttpClient client =newOkHttpClient();Requestrequest=newRequest.Builder()
.url("http://publicobject.com/helloworld.txt")
.build();
client.newCall(request).enqueue(newCallback() {
@Overridepublicvoid onFailure(Callcall, IOException e) {Log.d("OkHttp","Call Failed:"+ e.getMessage());
}
@Overridepublicvoid onResponse(Callcall,Responseresponse) throws IOException {Log.d("OkHttp","Call succeeded:"+response.message());
}
});
异步中的call.enqueue(new Callback(){})
当HttpClient的请求入队时,根据代码,我们可以发现实际上是Dispatcher进行了入队操作。
如果满足条件:
当前请求数小于最大请求数(64)
对单一host的请求小于阈值(5)
将该任务插入正在执行任务队列,并执行对应任务。如果不满足则将其放入待执行队列。
从之前的笔记中已经看过AsyncCall的execute()方法了
execute当任务执行完成后,无论成功与否都会调用dispatcher.finished方法,通知分发器相关任务已结束:
finish空闲出多余线程,调用promoteCalls调用待执行的任务
如果当前整个线程池都空闲下来,执行空闲通知回调线程(idleCallback)
接下来看看promoteCalls:
promoteCallspromoteCalls的逻辑也很简单:扫描待执行任务队列,将任务放入正在执行任务队列,并执行该任务。
三、 总结
以上就是整个任务队列的实现细节,总结起来有以下几个特点:
OkHttp采用Dispatcher技术,类似于Nginx,与线程池配合实现了高并发,低阻塞的运行
Okhttp采用Deque作为缓存,按照入队的顺序先进先出
OkHttp最出彩的地方就是在try/finally中调用了finished函数,可以主动控制等待队列的移动,而不是采用锁或者wait/notify,极大减少了编码复杂性
讲解及图片来源OKHttp源码分析——任务队列
网友评论