让我们深入研究 SerialExecutor 类。在这个类中,我们有 final
ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
这实际上作为不同线程上不同请求的序列化程序。这是一个半同步半异步模式的例子。
现在让我们看看串行执行器是如何做到这一点的。请看一下 SerialExecutor 的代码部分,写成
if (mActive == null) {
scheduleNext();
}
所以当第一次在 Asynctask 上调用 execute 时,这段代码在主线程上执行(因为 mActive 将被初始化为 NULL),因此它会将我们带到 scheduleNext() 函数。
ScheduleNext()函数写成如下:
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
所以在 schedulenext() 函数中,我们使用已在 dequeue 末尾插入的 Runnable 对象初始化 mActive。这个 Runnable 对象(它只是 mActive)然后在从线程池中取出的线程上执行。在那个线程中,“finally”块被执行。
现在有两种情况。
已经创建了另一个 AsyncTask 实例,我们在执行第一个任务时调用它的 execute 方法。
在执行第一个任务时,在 AsyncTask 的同一实例上第二次调用 Execute 方法。
场景一:如果我们查看SerialExecutor的execute函数,我们会发现我们实际上创建了一个新的可运行线程(比如说线程t)来处理后台任务。
在那个线程 t 中,我们运行mActive 的运行函数。但是由于它在 try 块中,finally 只会在该线程中的后台任务完成后才会执行。 (记住 try 和 finally 都发生在 t 的上下文中)。
在 finally 块中,当我们调用 scheduleNext 函数时,mActive 变为 NULL,因为我们已经清空了队列。但是,如果创建了相同AsyncTask 的另一个实例并且我们对它们调用execute,则这些AsyncTask 的execute 函数将不会执行,因为execute 之前的同步关键字以及SERIAL_EXECUTOR 是静态的实例(因此同一个类的所有对象将共享同一个实例……它是类级别锁定的一个示例)我的意思是同一个 AsyncTask 类的任何实例都不能抢占执行函数(因此,正在运行的后台任务线程 t)。这一切意味着只有一个活动线程运行该任务。对于不同的任务,这个线程可能不一样,但一次只有一个线程会执行该任务。因此,只有在第一个任务完成时,后面的任务才会一个接一个地执行,这就是为什么它被称为SerialExecutor。
场景二:在这种情况下,我们会得到一个异常错误。要了解为什么不能在同一个 Asynctask 对象上多次调用 execute 函数,请查看以下代码 sn-p 取自 executeOnExecutor in AsyncTask.java 尤其是在下面提到的部分:
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
从上面的代码 sn-p 可以清楚地看出,如果我们在任务处于运行状态时调用执行函数两次,它会抛出 IllegalStateException 说“无法执行任务:任务已经在运行。”。
你可以阅读我关于 AsyncTask 内部的讨论
https://docs.google.com/document/d/1_zihWXAwgTAdJc013-bOLUHPMrjeUBZnDuPkzMxEEj0/edit?usp=sharing