让我们深入了解 Android 的 Asynctask.java 文件,从设计者的角度来理解它,以及它是如何在其中很好地实现 Half Sync-Half Async 设计模式的。
在类的开头几行代码如下:
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
第一个是负责创建工作线程的 ThreadFactory。这个类的成员变量是到目前为止创建的线程数。在它创建一个工作线程的那一刻,这个数字会增加 1。
接下来是BlockingQueue。正如您从 Java 阻塞队列文档中知道的那样,它实际上提供了一个线程安全的同步队列,实现了 FIFO 逻辑。
接下来是一个线程池执行器,它负责创建一个工作线程池,可以在需要时执行不同的任务。
如果我们查看前几行,我们会知道 Android 将最大线程数限制为 128(从 private static final int MAXIMUM_POOL_SIZE = 128 可以看出)。
现在下一个重要的类是 SerialExecutor,它的定义如下:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
Asynctask 中接下来重要的两个函数是
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
和
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
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)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
从上面的代码可以清楚地看出,我们可以从 Asynctask 的 exec 函数中调用 executeOnExecutor ,在这种情况下,它需要一个默认的执行器。如果我们深入 Asynctask 的源代码,我们会发现这个默认的执行器只不过是一个串行执行器,其代码已在上面给出。
现在让我们深入研究 SerialExecutor 类。在这堂课中,我们有finalArrayDeque<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 方法。
场景一:如果我们看一下Serial Executor的execute函数,我们会发现实际上我们新建了一个可运行线程(Say thread t)来处理后台任务。看下面的代码片段——
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
从该行可以清楚地看出mTasks.offer(new Runnable)
,每次调用 execute 函数都会创建一个新的工作线程。现在,您可能能够找出 Half Sync - Half Async 模式与 SerialExecutor 的功能之间的相似之处。不过,让我澄清一下这些疑问。就像 Half Sync - Half Async 模式的异步层一样,
mTasks.offer(new Runnable() {
....
}
部分代码在调用执行函数时创建一个新线程并将其推送到队列(mTasks)。它是绝对异步完成的,因为它在将任务插入队列的那一刻,函数返回。然后后台线程以同步方式执行任务。所以它类似于半同步 - 半异步模式。对?
然后在那个线程 t 中,我们运行 mActive 的 run 函数。但是由于它在 try 块中,finally 只会在该线程中的后台任务完成后才会执行。(记住 try 和 finally 都发生在 t 的上下文中)。在 finally 块中,当我们调用 scheduleNext 函数时,mActive 变为 NULL,因为我们已经清空了队列。但是,如果创建了同一个 Asynctask 的另一个实例并且我们在它们上调用 execute,这些 Asynctask 的执行函数不会被执行,因为执行前的同步关键字,也因为 SERIAL_EXECUTOR 是一个静态实例(因此同一个类的所有对象将共享同一个实例......这是一个类级锁定的例子)我的意思是同一个 Async 类的任何实例都不能抢占在线程 t 中运行的后台任务。即使线程被某些事件中断,再次调用 scheduleNext() 函数的 finally 块也会处理它。这一切都意味着只有一个活动线程运行任务。对于不同的任务,这个线程可能不一样,但一次只有一个线程会执行该任务。因此,只有在第一个任务完成时,后面的任务才会一个接一个地执行。这就是为什么它被称为 SerialExecutor。即使线程被某些事件中断,再次调用 scheduleNext() 函数的 finally 块也会处理它。这一切都意味着只有一个活动线程运行任务。对于不同的任务,这个线程可能不一样,但一次只有一个线程会执行该任务。因此,只有在第一个任务完成时,后面的任务才会一个接一个地执行。这就是为什么它被称为 SerialExecutor。即使线程被某些事件中断,再次调用 scheduleNext() 函数的 finally 块也会处理它。这一切都意味着只有一个活动线程运行任务。对于不同的任务,这个线程可能不一样,但一次只有一个线程会执行该任务。因此,只有在第一个任务完成时,后面的任务才会一个接一个地执行。这就是为什么它被称为 SerialExecutor。
场景二:在这种情况下,我们会得到一个异常错误。要了解为什么不能在同一个 Asynctask 对象上多次调用执行函数,请查看以下代码片段,该代码片段取自 Asynctask.java 的 executorOnExecute 函数,尤其是在下面提到的部分:
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)");
}
}
从上面的代码片段可以清楚地看出,如果我们在任务处于运行状态时调用执行函数两次,它会抛出 IllegalStateException 说“无法执行任务:任务已经在运行。”。
如果我们想要并行执行多个任务,我们需要调用 execOnExecutor 传递 Asynctask.THREAD_POOL_EXECUTOR (或者可能是用户定义的 THREAD_POOL 作为 exec 参数。
你可以在这里阅读我关于 Asynctask 内部的讨论。