一起了解Android开源框架&&解析okhttp框架任务核心类dispatcher
Posted 冬天的毛毛雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一起了解Android开源框架&&解析okhttp框架任务核心类dispatcher相关的知识,希望对你有一定的参考价值。
OkHTTP的任务调度(Dispatcher)
简单描述
我们都知道同步和异步在外部方法调用区别是非常明显的,同步使用execute(),异步使用enqueue,在这之前我们可以考虑下两个问题
- okhttp如何实现同步异步请求?
主要通过dispatcher类来完成的,发送的同步/异步请求都会在dispatcher中管理其状态
- 到底什么是dispatcher?
dispatcher的作用为维护请求的状态,并维护一个线程池,用于执行请求
dispatcher源码实现
回答完上述问题相信就会有个非常直观的解释,Dispatcher就是维护我们的请求状态(包括了同步和异步),所以每当有网络请求,都会通过Call封装的dispatcher推送到就绪请求队列。而OkhTTP相对与别的网络框架来说,就优于在内部维护了一个线程池,能够更高效的
定义队列
这几个队列的定义前面已经提及了,这里有必要重新回顾一下,因为比较重要
/** Executes calls. Created lazily. */
private @Nullable ExecutorService executorService;
/** Ready async calls in the order they'll be run. */
private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();
/** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();
/** Running synchronous calls. Includes canceled calls that haven't finished yet. */
private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();
-
首先看readyAsyncCall,它是就绪状态的异步请求队列,当我们整个异步请求不满足条件的时候,就会进入到就绪异步请求队列中,进行缓存,当条件再满足的时候就会放到正在执行的异步请求队列中进行执行
-
然后是runningAyncCalls,表示的是正在执行的异步请求队列,看下它的注释,需要注意的是它包含了已经取消但没有执行完的请求
还有个比较重要的线程池executorService,因为dispatcher正是通过这个线程池来维护了整个异步请求,进行高效的网络请求
说到这里,可以思考下,为什么异步请求需要两个队列呢?
其实这里可以理解成为生产者/消费者模型,Dispatcher作为生产者,ExecutorService作为消费者池,既然是消费者/生产者模型,那必然需要两个队列来存放执行异步请求和等待的异步请求,一个Deque缓存,一个Deque正在执行的任务;
看下同步和异步请求中Dispatcher的使用
同步请求(call.execute())
/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
- 这里我们可以看到同步请求dispatcher做的非常简单,就是集合的添加操作
- 每当有新的同步请求的时候,dispatcher会直接将它加入到同步的正在执行的队列中
异步请求(call.enqueue())
异步请求对于dispatcher的使用要比同步请求复杂一些
- 前面我们已经请求了异步请求也是通过dispatcher的enqueue()方法进行的,可以看下它的源码
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
-
首先满足两个条件(一个是正在运行异步请求队列个数要小于最大可请求个数,一个是当前请求队列Host下的请求数一定要小于我们给它设定的最大值),这个时候就可以把封装好的AsyncCall添加到我们正在执行的异步队列当中,然后交给线程池执行,这里的线程池会负责我们自动创建,销毁,以及一些线程的管理.
-
如果不满足上述条件,直接添加到就绪异步等待队列当中,进行缓存等待操作.
-
其实异步请求也不算复杂,但是它通过一个dispatcher来封装好了请求管理,帮助我们做了相应的工作
-
接下来我们来看下executorService()线程池的执行
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
其实这个和一般线程池都是类似的,需要注意的是它的前三个参数的设定
- 第一个代表核心线程池的数量,把它设定为0,因为空闲一段时间后,需要把我们所用到的线程全部销毁
- 第二个参数表示最大的线程池,我们把它设定为整型的最大值,这是因为当我们的请求过来的时候,可以无限的扩充线程最大值,当然这是理论上这么说,但是okhttp的请求受到maxRequest变量的限制,所以也不是* 无限的创建线程池的
- 第三个参数表示的是当我们的线程数大于核心线程数的时候,那么空闲线程最大的存活时间就是60秒
说到这里,可能很多小伙伴不太理解为什么线程池要设定这三个参数,到底意味着什么,这里我简单举个例子
在我们实际运行中,我们需要开启20个并发请求,这时候线程池也会创建20个线程,那么当工作完成之后,线程池就会在60秒之后相继关闭所有无用的线程,这就是它线程池所设定这三个参数的意义所在
- 综上所诉,Call执行完肯定需要在runningAsyncCalls队列中移除这个线程的,那么就需要思考一个问题了:那么readySsyncCalls队列中的线程在什么时候才会执行呢?
还是要回到源码当中,可以看下enqueue方法传入的封装请求的AsyncCall,毋庸置疑,前面我们已经知道它是个Runnable,这样的话它执行的时候一定是放在线程池中的,可以追溯下AsyncCall这个线程执行方法
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
eventListener.callFailed(RealCall.this, e);
responseCallback.onFailure(RealCall.this, e);
}
} finally {
client.dispatcher().finished(this);
}
}
}
dispatcher里的finish方法
上述代码前面部分已经在之前的文章中解释过来,这里我站在dispatcher的角度来看的话,可以看到有个finally,无论有没有异常它都会执行,而里面主要是执行dispatcher里的finish方法
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
第一步就是调用了calls.remove(),正是去删除我们正在执行的异步请求
第二步就是promoteCalls(),这个方法其实就是调整我们的任务队列,无论是异步还是同步的请求队列它都是线程不安全;所以我们调用这个方法的时候都是在synchronized代码块中执行
判断正在执行的请求数量是否等于0和idleCallback不允许为空,满足这个条件后才继续执行
看下finished方法中的promoteCalls()实现
我们上面知道了finish做了主要的操作,看下promoteCalls()方法可以发现,原来就是在这里对我们的缓存等待异步请求的队列进行调度的
private void promoteCalls() {
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
可以看到它会先对原来的异步请求队列进行遍历,调用它的remove()删除最后一个元素之后,然后加入到正在执行的异步请求队列当中,这就是promoteCalls方法的作用
尾言
至此,以上就是我对于Okhttp中的任务调度Dispatcher做出的总结,接下来我将会分析Okhttp中的拦截器,作为okhttp核心的部分,也是okhttp中提供的一种
强大机制
最后小编分享一份,我在学习提升时,从网上收集整理了一些 android 开发相关的学习文档、面试题、Android 核心笔记等等文档,希望能帮助到大家学习提升,如有需要参考的可以直接去我 CodeChina地址:https://codechina.csdn.net/u012165769/Android-T3 访问查阅。
以上是关于一起了解Android开源框架&&解析okhttp框架任务核心类dispatcher的主要内容,如果未能解决你的问题,请参考以下文章
Android开源实战:手把手带你实现一个简单好用的搜索框(含历史搜索记录)
[android] smartimageview&常见的开源代码