AsyncTask 任务生命周期
AsyncTask 看起来像四个回调:onPreExecute()、doInBackground()、onProgressUpdate() 和 onPostExecute()。真正决定行为的却是另一条链:execute() 先改变一次性状态,再把 FutureTask 交给执行器;后台 Callable 完成后把结果封装成 Message,由一个绑定回调 Looper 的 Handler 把控制权送回目标线程。
本文面向已经读过 Handler 消息发送、 Handler 消息处理 和 HandlerExecutor 提交语义 的读者。重点不是回顾历史版本,而是回答一个可以 顺着源码复述的问题:一个 AsyncTask 实例从 execute() 到最终回调时,谁拥有任务、哪些字段改变、 取消和异常在哪个线程生效。文章只讨论 frameworks/base 中这份实现,不把 AndroidX 的 AsyncTask 替代品当作同一实现。
读完后,读者应能定位默认执行器和回调 Handler,解释为什么 execute() 只能调用一次,判断 cancel(true) 是否真的停止了后台代码,并区分“结果消息已经入队”和“回调已经完成”。
1. 入口契约
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
AsyncTask 的公开入口最终都汇聚到 executeOnExecutor()。execute() 只是选择当前的 sDefaultExecutor;Android 17 的默认值是全局的 SERIAL_EXECUTOR。
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
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;
}这里有三个容易被回调名称掩盖的事实:
| 时点 | 发生的事情 | 所在线程 |
|---|---|---|
进入 executeOnExecutor | 检查 mStatus,写入 RUNNING | 调用者;API 契约要求 UI 线程 |
onPreExecute() | 同步直接调用,不经过 Handler | 调用者线程 |
exec.execute(mFuture) | 只提交后台执行单元 | 由传入 Executor 决定 |
@MainThread 是调用约定,不是运行时线程检查。若 onPreExecute() 抛异常,状态已经写成 RUNNING,后续 exec.execute() 不会发生;该实例也不会自动回滚到 PENDING。这解释了为什么 AsyncTask 的一次性限制不仅是 API 设计,也是字段写入顺序产生的结果。
2. 对象组成
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
一个实例把业务输入、可取消的执行单元和回调通道拆成几个字段。mWorker 是真正实现 Callable<Result> 的对象,mFuture 则负责把它接到 Executor 的提交和取消协议。
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
private volatile Status mStatus = Status.PENDING;
private final AtomicBoolean mCancelled = new AtomicBoolean();
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
private final Handler mHandler;
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}字段的所有者和消费者可以这样分开看:
| 字段 | 写入者 | 消费者 | 生效时机 |
|---|---|---|---|
mWorker.mParams | executeOnExecutor | WorkerRunnable.call | 后台单元开始运行时 |
mStatus | 入口与 finish | 再次 execute、getStatus | 入口或结果消息分发时 |
mCancelled | cancel 或后台异常 | isCancelled、finish、publishProgress | 原子写入后立即可见 |
mTaskInvoked | WorkerRunnable.call | done | 后台 call 是否真正开始 |
mHandler | 构造函数 | postResult、publishProgress | 构造时固定回调 Looper |
mTaskInvoked 不是“任务完成”标志。它只表示 WorkerRunnable.call() 是否进入过;任务可能 已经开始但仍在运行,也可能在 call() 抛异常后被 done() 观察到。
3. 执行分流
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
默认执行器和显式并行执行器的差异,来自两个完全不同的队列模型。SERIAL_EXECUTOR 维护一个 进程内 ArrayDeque,前一个包装任务执行完毕后才调用 scheduleNext();THREAD_POOL_EXECUTOR 则使用 SynchronousQueue,最多保留 20 个工作线程。
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);
}
}
}串行包装器最终仍把当前 mActive 交给共享线程池。线程池本身的构造如下,它与 SerialExecutor 的等待队列承担不同职责。
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;
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;
}SynchronousQueue 不保存等待任务;当 20 个线程都不能接收新任务时,拒绝处理器才懒创建一个 5 线程的备用池和无界 LinkedBlockingQueue。因此“使用并行执行器”不等价于“永远最多 20 个 任务”,也不等价于“任务一定排队等待”;具体结果取决于当前线程是否空闲以及拒绝处理器是否被触发。
这张图表达的是提交分流,不是回调线程图。无论选哪种 Executor,结果回调仍由 mHandler 决定;THREAD_POOL_EXECUTOR 只改变 doInBackground() 的运行位置和并发关系。
4. 后台单元
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
构造函数创建 WorkerRunnable 和重写过的 FutureTask。call() 先把 mTaskInvoked 写为 true,再降低线程优先级并调用子类的 doInBackground()。无论正常返回还是抛出 Throwable, finally 都会调用 postResult(result)。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};这里的 catch (Throwable) 并不是把异常转成普通取消。它先把 mCancelled 设为 true,再 重新抛出;因此 FutureTask.done() 仍会看到执行失败,而主线程收到结果消息后会走 onCancelled(result)。真正的异常还会沿执行器线程的 FutureTask 路径继续传播,不能把 onCancelled 理解为异常已经被安全处理。
Binder.flushPendingCommands() 位于后台计算正常返回之后、结果消息入队之前。它不是结果 回调,也不是线程切换;它只处理当前线程待刷新的 Binder 命令。把它放在这里说明了结果回到 回调 Looper 之前,后台单元还存在一个 framework 清理步骤。
5. 回调通道
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
默认构造函数使用主 Looper;隐藏构造函数允许传入 Handler 或 Looper。当传入的是主 Looper 或 null 时,所有实例共享懒创建的 InternalHandler;其他 Looper 则各自创建 Handler。
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
// 随后创建 mWorker 和 mFuture
}结果和进度都使用同一个 Handler,但用不同的 what 值区分。AsyncTaskResult 把任务实例 也放入 Message.obj,所以消息被哪个 Handler 消费并不改变任务对象的归属。
private Result postResult(Result result) {
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}这两个入口只负责构造并投递消息;真正区分结果消息和进度消息的是回调 Handler 的 handleMessage() 分支。
private static class InternalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}sendToTarget() 只是把消息送入 Handler 绑定的 MessageQueue;它不等待目标线程执行。因此 doInBackground() 返回与 onPostExecute() 开始之间至少隔着一次消息入队和一次 Looper 分发。
6. 结果收束
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
结果消息最终进入 finish()。它在读取 mCancelled 后选择取消回调或正常回调,最后才把状态 写为 FINISHED。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}这个顺序带来两个边界:
onPostExecute()尚未开始时调用cancel(false),即使cancel()返回false,只要finish()读取到的mCancelled为true,也不会再选择onPostExecute()。- 如果
onCancelled()或onPostExecute()自身抛出异常,mStatus = FINISHED不会执行, 状态可能停留在RUNNING。因此FINISHED表示结果回调正常返回后的状态,不是后台计算 已经结束的全局事实。
状态和回调的关系可以压缩成下表:
| 状态/标志 | 代表什么 | 不代表什么 |
|---|---|---|
PENDING | 尚未通过入口提交 | 没有对象或没有回调 Handler |
RUNNING | 入口已接受一次执行并写入状态 | doInBackground 已开始或一定会完成 |
mTaskInvoked=true | WorkerRunnable.call 已进入 | 任务已成功返回 |
mCancelled=true | 取消请求或后台 Throwable 已标记 | 后台线程已经停止 |
FINISHED | finish 中的回调正常返回 | 所有排队的进度消息都已消费 |
7. 取消竞态
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
cancel() 先写原子取消标志,再委托 FutureTask.cancel();它不等待后台线程,也不清理已经 进入回调队列的进度消息。
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}可以把取消分成三种输入:
| 取消时机 | FutureTask 结果 | 后续源码路径 |
|---|---|---|
call() 尚未开始 | 任务不进入 call();done() 捕获 CancellationException | postResultIfNotInvoked(null),最终 onCancelled(null) |
call() 正在运行,mayInterruptIfRunning=false | 后台代码继续运行 | 只有 doInBackground() 主动检查 isCancelled() 才能提前返回 |
call() 正在运行,mayInterruptIfRunning=true | 执行器尝试中断工作线程 | 中断是否终止业务由 doInBackground() 使用的阻塞 API 决定 |
publishProgress() 的检查只发生在“准备发送”这一刻。已经进入 MessageQueue 的进度消息不会 被 cancel() 主动移除,而 InternalHandler 的进度分支也没有再次读取 isCancelled()。 因此不要把“取消后不再调用 publishProgress”误写成“取消后所有进度回调都必然消失”;已经 排队的消息仍可能先被目标 Looper 消费。
8. 完成补偿
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
FutureTask.done() 与 WorkerRunnable.call() 都可能触发 postResult(),但通过 mTaskInvoked 避免重复发送:正常后台路径先在 finally 发结果,done() 读取到 true 后 不再补发;取消发生在 call() 之前时,done() 负责补发一个 null 结果。
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
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(null);
}
}
};
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}这段补偿逻辑解决的是“后台 Callable 从未进入”的结果通知,不是 exactly-once 事务协议。结果 消息仍可能在 Looper 退出时无法分发;回调抛异常也可能阻止状态收束。若业务要求可重试、持久化 或幂等,必须在任务自己的数据协议中实现,不能从 AsyncTask 的 Result 泛型推出这些保证。
9. 线程边界
源码文件:
frameworks/base/core/java/android/os/AsyncTask.javaframeworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/Looper.java
从源码顺序可以得到一条不含糊的线程路径:
调用线程
└─ executeOnExecutor
├─ onPreExecute
└─ Executor.execute(FutureTask)
└─ WorkerRunnable.call(执行器线程)
├─ doInBackground
└─ Handler.sendToTarget(回调消息入队)
└─ Looper → InternalHandler.handleMessage(回调 Looper 线程)
├─ onProgressUpdate
└─ finish → onCancelled / onPostExecute默认构造函数使最后一段落到主 Looper,但隐藏的 AsyncTask(Looper) 构造函数可以把回调送到 另一条 Looper。因而“AsyncTask 的回调总在主线程”是默认使用方式,不是这个类所有构造路径 都满足的绝对事实。
10. 代码验证
下面的验证不依赖 AndroidX;它们针对这份源码中可观察的状态和线程边界。
10.1 一次性入口
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
对应符号是 executeOnExecutor() 与 mStatus。
建立一个最小子类,在第一次 execute() 后再次调用 execute(),断言第二次抛出 IllegalStateException。关键输入是同一个实例而不是两个实例;断言对象是入口的 mStatus 检查,而不是后台线程是否已经开始。
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... ignored) {
return null;
}
};
task.execute();
assertThrows(IllegalStateException.class, () -> task.execute());这个实验不能证明后台任务完成,也不能证明 onPostExecute() 已运行;它只覆盖一次性状态门禁。
10.2 取消输入
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
对应符号是 cancel()、isCancelled() 与 finish()。
让 doInBackground() 在循环中记录线程 ID,并周期性检查 isCancelled();主线程调用 cancel(true) 后等待 onCancelled()。应断言取消回调发生在回调 Looper,后台循环是否提前 结束则取决于检查点和阻塞操作。
final CountDownLatch cancelled = new CountDownLatch(1);
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... ignored) {
while (!isCancelled()) {
Thread.yield();
}
return null;
}
@Override protected void onCancelled(Void ignored) {
assertEquals(Looper.getMainLooper(), Looper.myLooper());
cancelled.countDown();
}
};
task.execute();
task.cancel(true);
assertTrue(cancelled.await(2, TimeUnit.SECONDS));这个输入证明的是协作式取消和回调线程;它不证明任意网络、文件或数据库调用都会响应中断。
10.3 源码搜索
在源码树中复查主线时,先从状态和两个消息常量入手,再扩展到执行器,能避免把回调顺序误读成 线程池顺序:
rg -n "executeOnExecutor|mStatus|mTaskInvoked|mCancelled|finish\(" \
frameworks/base/core/java/android/os/AsyncTask.java
rg -n "SERIAL_EXECUTOR|THREAD_POOL_EXECUTOR|sRunOnSerialPolicy|SynchronousQueue" \
frameworks/base/core/java/android/os/AsyncTask.java
rg -n "MESSAGE_POST_RESULT|MESSAGE_POST_PROGRESS|sendToTarget|handleMessage" \
frameworks/base/core/java/android/os/AsyncTask.java11. 替代边界
源码文件:frameworks/base/core/java/android/os/AsyncTask.java
类注释直接指出它存在的边界:它只是 Thread 与 Handler 的辅助类,不是通用线程框架;短任务 适用,长时间运行的线程应转向 java.util.concurrent 等通用工具。类本身在 Android 17 已标记 @Deprecated,注释列出的实际问题包括 Context 泄漏、配置变化后的回调丢失或崩溃、版本间行为 不一致、吞掉 doInBackground() 异常,以及相对于直接使用 Executor 缺少额外价值。
这些问题都能从本文的源码边界落地理解:任务对象不认识 Activity 生命周期,取消不会等待,回调 依赖一个仍然存活的 Looper,全局串行执行器会让无关任务互相排队,异常也没有提交者可读取的 Future 结果。替代方案的选择应从需求反推:需要线程归属时使用明确的 Handler/Executor, 需要结果和取消协议时使用 Future 或应用自己的任务状态,生命周期关联则由上层组件负责。
当读者再次看到旧代码中的 AsyncTask,可以按四个问题判断风险:谁持有任务对象,哪个执行器 真正运行 doInBackground(),哪个 Looper 消费结果消息,以及取消后哪些工作仍可能继续。只要 这四个答案都能回到源码字段和调用链,便不会把“回调 API”误当成完整的生命周期管理器。
