AsyncTask 的介绍及使用

       Android 中的线程形态除了传统的 Thread 以外,还包含 AsyncTask、HandlerThread 以及 IntentService,这三者的底层实现也是线程,但是它们具有特殊的表现形式,同时在使用上也各有优缺点。为了简化在子线程中访问 UI 的过程,系统提供了 AsyncTask,AsyncTask 经过几次修改,导致对于不同的 API 版本 AsyncTask 具有不同的表现,尤其是多任务的并发执行上。

AsyncTask

       AsyncTask 是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新 UI。从实现上来说,AsyncTask 封装了 Thread 和 Handler,通过 AsyncTask 可以更加方便地执行后台任务以及在主线程中访问 UI,但是 AsyncTask 并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

       AsyncTask 是一个抽象的泛型类,它提供了 Params、Progress和 Result 这三个泛型参数,其中 Params 表示参数的类型,Progress 表示后台任务的执行进度的类型,而 Result 则表示后台任务的返回结果的类型,如果 AsyncTask 确实不需要传递具体的参数,那么这三个泛型参数可以用 Void 来代替。AsyncTask 这个类的声明如下所示:

1
public abstract class AsyncTask<Params, Progress, Result>

       AsyncTask提供了4个核心方法,它们的含义如下所示:

  • onPreExecute(),在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做一些准备工作。

  • doInBackground(Params…params),在线程池中执行,此方法用于执行异步任务,params 参数表示异步任务的输入参数。在此方法中可以通过 publishProgress 方法来更新任务的进度,publishProgress 方法会调用 onProgressUpdate 方法。另外此方法需要返回计算结果给 onPostExecute 方法。

  • onProgressUpdate(Progress…values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用。

  • onPostExecute(Result result),在主线程中执行,在异步任务执行之后,此方法会被调用,其中 result 参数是后台任务的返回值,即 doInBackground 的返回值。

       上面这几个方法,onPreExecute 先执行,接着是 doInBackground,最后才是 onPostExecute。除了上述四个方法以外,AsyncTask 还提供了 onCancelled() 方法,它同样在主线程中执行,当异步任务被取消时,onCancelled() 方法会被调用,这个时候 onPostExecute 则不会被调用。下面提供一个典型的示例,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
protected Long doInBackground(URL... params) {
int count = params.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(params[0]);
publishProgress((int) (i / (float) count) * 100);
// Escape early if cancel() is called
if (isCancelled())
break;
}
return totalSize;
}

@Override
protected void onProgressUpdate(Integer... values) {
setProgressPercent(values[0]);
}

@Override
protected void onPostExecute(Long result) {
showDialog("Downloaded" + result + "bytes");
}
}

       在上面的代码中,实现了一个具体的 AsyncTask 类,这个类主要用于模拟文件的下载过程,它的输入参数类型为 URL,后台任务的进程参数为 Integer,而后台任务的返回结果为 Long 类型。注意到 doInBackground 和 onProgressUpdate 方法它们的参数中均包含“…”的字样,在 Java 中“…”表示参数的数量不定,它是一种数组型参数。当要执行上述下载任务时,可以通过如下方式来完成:

1
new DownloadFilesTask().execute(url1, url2, url3);

       在 DownloadFilesTas k中,doInBackground 用来执行具体的下载任务并通过 publishProgress 方法(这个是 AsyncTask 的方法)来更新下载的进度,同时还要判断下载任务是否被外界取消了。当下载任务完成后,doInBackground 会返回结果,即下载的总字节数。需要注意的是,doInBackground 是在线程池中执行的。onProgressUpdate 用于更新界面中下载的进度,它运行在主线程,当 publishProgress 被调用时,此方法就会被调用。当下载任务完成后,onPostExecute 方法就会被调用,它也是运行在主线程中,这个时候可以在界面上做出一些提示,比如弹出一个对话框告知用户下载已经完成。

       AsyncTask 在具体的使用过程中也是有一些条件限制的,主要有如下几点:

  • AsyncTask 的类必须在主线程中加载,这意味着第一次访问 AsyncTask 必须发生在主线程,当然这个过程在 Android4.1 及以上版本中已经被系统自动完成。在 Android5.0 的源码中,可以查看 ActivityThread 的 main 方法,它会调用 AsyncTask 的 init 方法,这就满足了 AsyncTask 的类必须在主线程中进行加载这个条件了。

  • AsyncTask的对象必须在主线程中创建。

  • execute方法必须在UI线程调用。

  • 不要在程序中直接调用 onPreExecute、onPostExecute、doInBackground 和 onProgressUpdate 方法。

  • 一个 AsyncTask 对象只能执行一次,即只能调用一次 execute 方法,否则会报运行时异常。

  • 在 Android 1.6 之前,AsyncTask 是串行执行任务的,Android1.6 的时候 AsyncTask 开始采用线程池处理并行任务,但是从 Android3.0 开始,为了避免 AsyncTask 所带来的并发错误,AsyncTask 又采用一个线程来串行执行任务。尽管如此,在 Android3.0 以及后续的版本中,仍然可以通过 AsyncTask的 executeOnExecutor 方法来并行地执行任务。

参考资料:
《Android 开发艺术探索》任玉刚 第11章 11.2 11.2.1 AsyncTask

Fork me on GitHub