前言:AsyncTask 是 Android SDK 中提供的一个用于执行异步任务的框架,在 Android 兴起的早期被广泛使用,但如今在 API 30 被弃用,取而代之的是 java.util.concurrent 或 Kotlin 协程。虽然它存在着一些不足,但是我们还是可以尝试了解一下它的实现原理以及存在的不足。
注:本文以 API 29 的 AsyncTask 源码进行解析。
功能概述
首先,让我们来简单地了解一下它的设计初衷:
AsyncTask
的设计初衷是能够帮助用户方便地完成异步任务的线程调度,它对用户提供了如下几个接口:
public abstract class AsyncTask<Params, Progress, Result> {
@WorkerThread
protected abstract Result doInBackground(Params... params);
@MainThread
protected void onPreExecute() {
}
@MainThread
protected void onPostExecute(Result result) {
}
@MainThread
protected void onProgressUpdate(Progress... values) {
}
@MainThread
protected void onCancelled(Result result) {
onCancelled();
}
}
首先看到它的三个范型参数:
- Params:代表传递给它的参数类型
- Progress:代表用于反馈进度的数据类型
- Result:代表异步执行的结果类型
用户可以通过实现doInBackground
方法来编写在异步线程所要进行的处理;通过onPostExecute
方法的重写实现对异步请求结果的获取;通过onPreExecute
实现了在doInBackground
之前进行一些预处理;通过onProgressUpdate
实现对进度的监听;通过onCancelled
实现对任务中断的监听。
当我们编写一个AsyncTask
后,只需要调用它的execute
方法即可,而背后的线程调度的过程都会由它替我们完成,看上去是十分美好的。
同时从上面的代码中可以看到,每个方法都有被@MainThread
的注解标注,这些注解主要的作用是用来标注这个方法运行所处的线程。可以看出,只有doInBackground
是在异步线程进行执行。
创建
让我们看看它的创建过程,来到它的构造函数:
public AsyncTask() {
this((Looper) null);
}
它的无参构造函数转调到了它的有参构造函数,这个构造函数需要以Looper
作为一个参数:
public AsyncTask(@Nullable Looper callbackLooper) {
// 根据传递进来的 Looper 来构建了一个 Handler,若没有指定 Looper 或指定的 Looper 是主线程的 Looper,
// 则指定内置的 InternalHandler 对消息进行处理(见附1),否则构造一个对应的 Handler。
// 可以看出 AsyncTask 不是一定回到主线程的。
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
// 构建了一个 WorkerRunnable,WorkerRunnable 实际上是对 Callable 的简单包装(见附2),
// 区别仅仅在于可以传入参数。
mWorker = new WorkerRunnable<Params, Result>() {
// 当 mWorker 被执行时
public Result call() throws Exception {
// 将当前 Task 设置为被执行
mTaskInvoked.set(true);
Result result = null;
try {
// 进行线程优先级的设置
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 调用 doInBackground 方法并获取了返回值
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
// 看来这个 WorkerRunnable 是在异步线程中执行的。
// 不论成功与否,它最后都会调用 postResult 进行结果的交付。
postResult(result);
}
// 返回结果
return(result);
}
};
// 基于前面的 mWorker 构建了一个 FutureTask
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
// 当执行完成或被取消时,会调用 postResultIfNotInvoked 方法传入 mWorker 的执行结果。
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
// 当执行完成或被取消时,会调用 postResultIfNotInvoked 方法传入 mWorker 的执行结果。
postResultIfNotInvoked(null);
}
}
};
}
附1
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return(sHandler);
}
}
附2
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
总的来说,其构造过程主要是对 Handler、WorkerRunnable 以及 FutureTask 进行了构建。
执行
让我们看看当调用execute
之后它做了什么:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params...params) {
return(executeOnExecutor(sDefaultExecutor, params));
}
可以看到,它转调到了executeOnExecutor
方法,并且传入了一个Executor
对象sDefaultExecutor
,我们先不去关注这个Executor
的设计,让我们先看看executeOnExecutor
方法:
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params...params) {
// 首先,它对当前 Task 的状态进行了检查,AsyncTask 共有三种状态(见附1)
// 只有 PENDING 的 Task 才能被 execute。
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)");
}
}
// 改变当前 Task 的状态
mStatus = Status.RUNNING;
// 调用 onPreExecute 方法进行了用户实现的预处理
onPreExecute();
// 将用户的参数交给 mWorker
mWorker.mParams = params;
// 将 mFuture 交给了传入的线程池进行处理
exec.execute(mFuture);
return(this);
}
附1
public enum Status {
// 待执行
PENDING,
// 正在执行
RUNNING,
// 已执行
FINISHED,
}
FutureTask
执行后,会使得mWorker
被执行,它执行后会将mTaskInvoked.set(true)
,并调用postResult
进行结果的交付:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return(result);
}
这里实际上就是把结果包装成了AsyncTaskResult
类并放入了消息队列,这样就实现了线程的切换,将消息通过Handler
由子线程发送给了主线程(指AsyncTask
被指定的线程),当Handler
收到消息后,就会对消息进行处理。
回到AsyncTask
创建的时候,我们可以看到在构造时没有指定Looper
的情况下,默认的InternalHandler
是如何进行处理的:
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return(sHandler);
}
}
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
// 在 MESSAGE_POST_RESULT 消息下,它在收到 AsyncTaskResult 后会调用 finish 方法进行整个任务
// 的完成处理(见附1)
case MESSAGE_POST_RESULT :
/* There is only one result */
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS :
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
附1
private void finish(Result result) {
// 若任务已取消,它会调用 onCancelled 方法进行回调
if (isCancelled()) {
onCancelled(result);
}
// 若任务正常完成,它会调用 onPostExecute 方法
else {
onPostExecute(result);
}
// 设置状态为 FINISHED
mStatus = Status.FINISHED;
}
当 FutureTask 执行完成后,会调用postResultIfNotInvoked
方法,并传入mWorker
的执行结果:
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
可以看到,这里实际上是在Task
没有被Invoke
的情况下才会调用到,由于mWorker
在被执行时首先就会将wasTaskInvoked
置为 true,因此实际上这里很少能被调用到。
我们再看看onProgressUpdated
何时会被调用到,我们可以找到MESSAGE_POST_PROGRESS
的消息是何时被发出的:
@WorkerThread
protected final void publishProgress(Progress...values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values )).sendToTarget();
}
}
它是在publishProgress
方法中被调用的,这个方法是提供给用户的,因此进度的更新需要用户自行在doInBackground
中调用publishProgress
从而实现。
取消
我们看看任务的取消,看到cancel
方法:
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
// cancel 方法最后会调用到 mFuture 的 cancel,并且它需要传递一个参数 mayInterruptIfRunning,
// 见附1
return(mFuture.cancel(mayInterruptIfRunning));
}
附1
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return(false);
try {
// 当 mayInterruptIfRunning 为 true 时,即使任务在运行时也会被打断。
// 而如果为 false,则 doInBackground 仍然会一直执行到结束。
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally {
U.putOrderedInt(this, STATE, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return(true);
}
这个设计非常奇怪,按道理来说取消的场景只有任务正在执行时需要取消,完全可以提供一个这个参数默认为 true 的方法供外部调用。并且这个方法只能尽量让任务尽快结束,如果执行的过程中有一些不可打断的操作,则这个方法调用后仍然不会使得任务停止。
线程池
对于AsyncTask
,我们还有最后一个问题没有研究,那就是它执行任务的sDefaultExecutor
:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
可以看到它的默认值为SERIAL_EXECUTOR
。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
SERIAL_EXECUTOR
是SerialExecutor
的实例。
private static class SerialExecutor implements Executor {
// Executor 内部维护了一个 ArrayDeque 队列,这个队列是一个任务缓存队列,
// 里面存放了对真正要执行的 Runnable 进行包装的 Runnable。
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();
}
}
} );
// 当 mActive 为 null,它会调用 scheduleNext 方法。
if (mActive == null) {
scheduleNext();
}
}
// 在 scheduleNext 方法中会从 mTasks 队首取出任务赋值给 mActive,
// 之后通过 THREAD_POOL_EXECUTOR 这个线程池对该任务进行执行。
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
当任务执行完成后,会继续调用scheduleNext
对队列中的下一个任务进行执行。通过这样的设计,Runnable
的执行变成了一种按序执行,只有前一个执行结束,才会进行下一个任务的执行,因此AsyncTask
的执行顺序实际上是一种串行执行。
那么THREAD_POOL_EXECUTOR
又是一个怎样的线程池呢?让我们看看它的声明:
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
可以看到,THREAD_POOL_EXECUTOR
的配置如下:
- CORE_POOL_SIZE:内部核心线程个数
- MAXIMUM_POOL_SIZE:最大线程数量
- KEEP_ALIVE_SECONDS:每一个线程空闲后等待的时间,时间到了就会被停止
只保留一个池线程。如果需要,让池增长到相当多的线程,但让他们很快超时。 在不太可能发生线程用尽的情况下,退回到一个简单的无界队列执行程序。
这种组合可确保:
- 保持少量的线程
- 仅在启动明显更大,但仍有限的线程集之后才进行排队
- 将线程总数限制为有界,但仍然允许无限制的任务集排队。
不足之处
AsyncTask
看似十分美好,但实际上存在着非常多的不足,这些不足使得它在 API 30
被废弃:
- 生命周期:
AsyncTask
没有与Activity
、Fragment
的生命周期绑定,即使Activity
被销毁,它的doInBackground
任务仍然会继续执行。 - 取消任务:
AsyncTask
的cancel
方法的参数mayInterruptIfRunning
存在的意义不大,并且它无法保证任务一定能取消,只能尽快让任务取消(比如,如果正在进行一些无法打断的操作时,任务就仍然会执行) - 内存泄漏:由于它没有与
Activity
等生命周期进行绑定,因此它的生命周期仍然可能比Activity
长,如果将它作为Activity
的非static
内部类,则它会持有Activity
的引用,导致Activity
的内存无法释放。
总结
本文基本上对AsyncTask
的实现原理有一个大致的了解了,它的原理实际上还是挺简单的:通过Executor
与FutureTask
配合,从而实现任务的异步执行,最后在任务结束后通过Handler
进行线程的切换从而实现了整个线程调度的功能。在Android 3.0
之后,默认情况下AsyncTask
的执行顺序是串行进行的。
© 2020 Tyhoo Wu, All rights reserved.