AsyncTask 的工作原理
本篇介绍 AsyncTask 的使用方法和工作原理。
概述
- 线程分为主线程和子线程,子线程执行耗时操作。
- 除了 Thread 之外,AsyncTask 和 IntentService 还有 HandlerThread 也是一些特殊的线程。
- AsyncTask 封装了线程池和 Handler ,方便在子线程中更新 UI。
- HandlerThread 是一种具有消息循环的线程,在其内部可以使用 Handler。
- IntentService 是一个服务,其内部是 HandlerThread 来执行任务,完成后自动退出。虽然其作用很像一个后台线程,但是最终其还是一个服务。不容易被系统杀死,从而保证后台任务的执行。
- Android 中也引入了线程池的概念,这样可以减少频繁的创建和销毁线程。
- Android 中线程也分为主线程和子线程,它们之间的作用区别很明显。
AsyncTask 使用
AsyncTask 的使用方式如下实例 Demo:
class DownloadTask extends AsyncTask<URL, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... params) {
try {
while (true) {
int downloadPercent = doDownload();
publishProgress(downloadPercent); // 子线程切换至 UI 线程
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("当前下载进度:" + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) { // 提示任务执行结果
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
}
}
}
而我们调用时候只需要如下简单语句即可:
new DownloadTask().execute(url1,url2,url3);
对于上述示例,我们注意以下几点:
- doInBackgroud 是在线程池中执行的。
- onProgressUpdate 用于更新界面中的下载进度,运行在主线程中,当 publishProgress 被调用时,此方法就会被调用。不像 Handler 中需要发送和接受消息来切换线程了。
- 当下载任务完成后,onPostExecute 方法就会被调用。 它也是运行在主线程中。
源码分析
程序的执行入口是 new AsyncTask().execute() 我们自然要看一下 AsyncTask 的构造方法执行了哪些初始化工作:
public AsyncTask() {
// mWorker 对象是一个 Callable 对象
mWorker = new WorkerRunnable<Params, Result>() {
// 该方法会在线程池中执行
public Result call() throws Exception {
// 表示任务已经执行过了
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 调用 doInBackgroud 方法并将结果返回至 postResult
return postResult(doInBackground(mParams));
}
};
// mFuture 对象是一个 FutureTask 对象
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//...
};
}
容易看出来初始化了两个对象。
接着我们进入 execute() 方法看一看,注意这里传入了 sDefaultExecutor 和 params:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
进入 executeOnExecutor( ) 方法看一看:
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
// task 状态的判断
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(); // 证明 onPreExecute 第一个被执行还在 UI 线程中...
mWorker.mParams = params; // params 传入 mWorker 而其与 mFuture 有关
exec.execute(mFuture); // 重点
return this;
}
上述代码中我们并没有看到 doInBackground() 方法。而唯一的突破口就是 exec.execute(mFuture) 了。我们可以很清晰的看到 exec 实际上是 sDefaultExecutor ,这是什么?我们自然要去该对象的 execute() 方法中看看了。在上面代码中我们将 mFuture 传入进了 execute() 方法中。FutureTask 是一个并发类,传入进去之后,其充当了 Runnable 对象:
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
// 实际上是一个串行的线程池用来排队的
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
// 定义任务对列,指定类型为 Runnable 对象
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
// 这里的 execute 就开始在子线程中执行了
public synchronized void execute(final Runnable r) {
// 插入到任务队列 mTask 中
mTasks.offer(new Runnable() {
public void run() {
try {
// 突破口
r.run();
} finally {
scheduleNext();
}
}
});
// 如果这时候没有活动的 Asynctask 任务就会执行下一个任务
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
// 这里可以看出 AT 是串行执行的,队列的 poll() 方法嘛
if ((mActive = mTasks.poll()) != null) {
// THREAD_POOL_EXECUTOR 是用来执行任务的
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
在上面的 sDefaultExecutor 实际上是一个串行的线程池。一个进程中所有的 AsyncTask 全部在这个串行的线程池中排队执行。
接着我们继续追踪 run() 直到发现这样一句:
result = callable.call();
这句是一开始初始化 AsyncTask 中构造函数中的。原来是一个回调机制啊,再次拿出来分析分析:
// mWorker 对象是一个 Callable 对象
mWorker = new WorkerRunnable<Params, Result>() {
// 该方法会在线程池中执行
public Result call() throws Exception {
// 表示任务已经执行过了
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 调用 doInBackgroud 方法并将结果返回至 postResult
return postResult(doInBackground(mParams));
}
};
接着进入 postResult( ) 方法中看一看:
private Result postResult(Result result) {
// 消息携带两个一个常量和一个执行结果
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
// 发送消息
message.sendToTarget();
return result;
}
这里使用 sHandler 对象发出了一条消息,消息中携带了 MESSAGE_POST_RESULT 和一个表示任务执行结果的 AsyncTaskResult 对象。这个 sHandler 对象是 InternalHandler 类的一个实例,那么这条消息肯定会在InternalHandler 的 handleMessage() 方法中被处理。InternalHandler 的源码如下所示:
// 静态类
private static class InternalHandler extends Handler {
//...
@Override
public void handleMessage(Message msg) {
AsyncTaskResult result = (AsyncTaskResult) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// 调用 finish 方法
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// 解释 publishProgress() 方法可以从子线程切换到UI线程
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
发现 Handler 是一个静态类。是为了能够将执行环境从子线程切换到主线程中。由于静态类的关系,更进一步要求 AsyncTask 的类必须在主线程中加载(这里不是很明白。静态成员会在加载类的时候进行初始化,这样同一个进程中的线程就可以共享资源了?)。sHandler 在收到 MESSAGE_POST_RESULT 后会调用 fininsh() 方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
// 状态标示
mStatus = Status.FINISHED;
}
这里就比较简单了。分取消与否调用 onCancelled() 和 onPostExecute() 方法。
四. 总结分析
AsyncTask 封装了 Thread 和 Handler, 通过 AsyncTask 可以更加方便的执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务。建议采用线程池。
提供了四个核心方法:
- onPreExecute():在主线程中执行,在异步任务执行前,此方法会被调用,用于做一些准备工作。
- doInBackground(Params…params):在线程池中执行,此方法用于执行异步任务。
- onProgressUpdate(Progress…value):在主线程中执行,当后台任务的执行进度发生变化时,此方法会被调用。
- onPostExecute(Result result):在主线程中执行,在异步任务执行之后,此方法会被调用。
- onCancelld():当异步任务被取消时,该方法会被调用。
对于使用AsyncTask 有以下几点需要注意的:
- AsyncTask 的类必须在主线程中加载。
- AsyncTask 的 对象 必须在 主线程中创建。
- execute 必须在 UI 线程中调用。
- 不要在程序中直接调用上述前四个方法。
- 在 Android 1.6 之前 AsyncTask 是串行执行任务的(可同时执行 5 个任务),那时候采用线程池来处理并行任务,但是从 3.0 开始为了避免 AT 所带来的并发错误,AT 又采用一个线程来串行执行任务(只能执行 1 个任务)。尽管如此,3.0 之后仍然可以通过 AT 的 executeOnExecutor 方法来并行执行任务。对于这一点的实验可以看 《Android 开发艺术探索》P403。
- 如果要深究为什么 Android 3.0 以上 AT 默认不并行执行,可以看这篇文章。