前言
Android应用的主线程(UI线程)肩负着绘制用户界面和及时响应用户操作的重任,为了避免”ANR”,就要确保主线程时刻保持较高的响应性.为了做到这一点,我们就要把耗时的任务移出主线程,那么耗时的任务交给谁来完成呢?答案就是工作者线程。Android开发中我们通常让主线程负责前台用户界面的绘制以及响应用户的操作,让工作者线程在后台执行一些比较耗时的任务。Android中的工作者线程主要有AsyncTask、IntentService、HandlerThread,它们本质上都是对线程或线程池的封装。
AsyncTask是我们日常中广泛使用的一种工作者线程,它的方便之处在于可以在后台任务执行完毕时根据返回结果相应的更新UI。下面我们来研究一下它的工作原理。
AsyncTask简介
AsyncTask是对Handler与线程池的封装。使用它的方便之处在于能够更新用户界面,当然这里更新用户界面的操作还是在主线程中完成的,但是由于AsyncTask内部包含一个Handler,所以可以发送消息给主线程让它更新UI。另外,AsyncTask内还包含了一个线程池。使用线程池的主要原因是避免不必要的创建及销毁线程的开销。设想下面这样一个场景:有100个只需要0.001ms就能执行完毕的任务,如果创建100个线程来执行这些任务,执行完任务的线程就进行销毁。那么创建与销毁进程的开销就很可能成为了影响性能的瓶颈。通过使用线程池,我们可以实现维护固定数量的线程,不管有多少任务,我们都始终让线程池中的线程轮番上阵,这样就避免了不必要的开销。
在这里简单介绍下AsyncTask的使用方法,为后文对它的工作原理的研究做铺垫,关于AsyncTask的详细介绍大家可以参考官方文档或是相关博文。
AsyncTask是一个抽象类,我们在使用时需要定义一个它的派生类并重写相关方法。AsyncTask类的声明如下:
1 | public abstract class AsyncTask<Params, Progress, Result> |
我们可以看到,AsyncTask是一个泛型类,它的三个类型参数的含义如下:
1 | Params:doInBackground方法的参数类型; |
我们再来看一下AsyncTask类主要为我们提供了哪些方法:
- onPreExecute() //此方法会在后台任务执行前被调用,用于进行一些准备工作
- doInBackground(Params… params) //此方法中定义要执行的后台任务,在这个方法中可以
- publishProgress来更新任务进度(publishProgress内部会调用onProgressUpdate方法)
- onProgressUpdate(Progress… values) //由publishProgress内部调用,表示任务进度更
- onPostExecute(Result result) //后台任务执行完毕后,此方法会被调用,参数即为后台任务的返回结果
- onCancelled() //此方法会在后台任务被取消时被调用
以上方法中,除了doInBackground方法由AsyncTask内部线程池执行外,其余方法均在主线程中执行。
AsyncTask的使用方法比较简单,无非是创建一个AsyncTask派生类对象,重写其doInBackground()函数,然后在合适时机调用这个对象的execute()或executeOnExecutor()函数即可。
1 | private static class MyTask extends AsyncTask<Void, Void, Void> { |
1 | private class TestClickListener implements View.OnClickListener { |
一般情况下,我们会像上面代码中这样调用AsyncTask的execute()函数,这样,投入执行的task会串行执行。不过,有时候我们也希望task们可以并行执行,此时只需把execute()换成executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)即可。
###AsyncTask的内部机制
AsyncTask本身是个抽象的泛型基类,正如前面所说,在实际使用时,我们必须定义它的派生类,并在实现AsyncTask派生类时,重写其doInBackground()成员函数。
作为一种异步执行的任务,AsyncTask是依靠内部的线程池来完成任务调度的。大体上说,AsyncTask内部搞了两个静态的执行器,分别表示成AsyncTask.THREAD_POOL_EXECUTOR 和 AsyncTask.SERIAL_EXECUTOR,前者是可并行执行的执行器(线程池),后者是串行执行的执行器(线程池)。
AsyncTask的构造函数如下:
1 | /** |
构造函数的注释中说的很明确,必须在UI线程里构造AsyncTask对象。而且构造函数里为两个重要的成员:mWorker和mFuture赋了值,这个我们后文再细说。
AsyncTask的execute()
我们先回过头看前文曾经提到的AsyncTask的execute()函数,其代码如下:frameworks/base/core/java/android/os/AsyncTask.java
1 | @MainThread |
因为params参数是可变长参数,所以execute()可以接受0到n个参数。注意,execute()和executeOnExecutor()都必须在UI线程里调用。
execute()只是简单地调用executeOnExecutor()而已,它传递的静态变量sDefaultExecutor引用的就是串行执行器AsyncTask.SERIAL_EXECUTOR:
1 | private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; |
executeOnExecutor()的代码截选如下:
1 | @MainThread |
也就是说,最终还是在调用执行器的execute()函数,只不过会把一个mFuture委托给执行器去回调。
默认情况下使用的串行执行器类是SerialExecutor,它的代码如下:
1 | private static class SerialExecutor implements Executor { |
从代码里可以看到,所谓的串行执行器内部,其实也是在复用THREAD_POOL_EXECUTOR,只不过利用对mActive的判断,把调用的流程改成串行的了。
SerialExecutor内部使用的是java.util.ArrayDeque队列,它的poll()函数可以检索并移除此队列的头部,如果返回null,则表示此队列已经取空了。每次摘取一个列头,并记录在mActive变量里,然后交给THREAD_POOL_EXECUTOR来处理。
ThreadPoolExecutor是java提供的线程池实现。总之,线程池会在后续的某个时刻,回调上面插入的Runnable对象的run()。在executeOnExecutor()函数里,我们已经看到向执行器添加了AsynctTask的mFuture成员,而mFuture本身实现了Runnable接口,以后回调就是回调mFuture的run()函数。
AsyncTask和线程池的协作
AsyncTask里的mFuture
AsyncTask的mFuture非常重要,它的定义如下:
1 | private final FutureTask<Result> mFuture; |
类型为FutureTask,其实现可以参考JDK里的代码:
【java/util/concurrent/FutureTask.java】
1 | public class FutureTask<V> implements RunnableFuture<V> |
【java/util/concurrent/FunnableFuture.java】
1 | public interface RunnableFuture<V> extends Runnable, Future<V> |
在前文列出AsyncTask构造函数时,我们已经看到mFuture的创建代码了,注意,在创建FutureTask对象时,传入了mWorker,它会被记入mFuture内部(如果分析JDK的代码,可以看到大体上就是记入mFuture.sync.callable了)。后续在被线程池执行时,这个mWorker才是最核心的对象。
欲了解详情,我们先得看看AsyncTask机制运用的线程池。在AsyncTask类里这样定义线程池成员的:
1 | private static final BlockingQueue<Runnable> sPoolWorkQueue = |
注意,线程池都是记在静态变量里的,它的生命期和进程的生命期基本一致。
细心的同学还记得,前文在定义AsyncTask派生类时,我们写的是private static class,大家不要忘记加static,否则就是写了一个普通内嵌类,而普通内嵌类对象内部会隐式地引用其外部类,这样当我们的task对象记入线程池后,就有可能导致task的外部类(很有可能是个Activity或Service)对象在较长时间内都不能被垃圾回收机制回收,从而导致内存泄漏。
本文的重点并不想太深入线程池的内部机理,我们只做必要的探讨即可。我们大体上只需知道线程池里的线程会执行FutureTask的run()函数即可。而FutureTask的run()代码如下:
【java/util/concurrent/FutureTask.java】
1 | public void run() { |
其中会调用callable.call(),这一步就会间接调用到AsyncTask的doInBackground()。再接下来,如果不出异常的话,会对call()返回的结果执行set()操作。大家还记得前文WorkerRunnable实现的call()函数吗?它最后返回语句为:return postResult(result);现在设置的就是postResult()返回的Result对象,其归根溯源就是doInBackground()返回的那个Result对象。
FutureTask的set()函数的代码如下:
【java/util/concurrent/FutureTask.java】
1 | protected void set(V v) { |
结果记录进Sync类的result成员,然后回调FutureTask的done()函数,这也就回调到前文我们看到的AysncTask的mFuture的done()函数了。我们再列一下mFuture的代码:
【frameworks/base/core/java/android/os/AsyncTask.java】
1 | mFuture = new FutureTask<Result>(mWorker) { |
done()里面做的无法一些善后处理。
1 | private void postResultIfNotInvoked(Result result) { |
AsyncTask里的mWorker
AsyncTask的另一个重要成员是mWorker,
1 | private final WorkerRunnable<Params, Result> mWorker; |
除了在executeOnExecutor()里会为mWorker的mParams成员赋值外,AsyncTask一般不会直接操作mWorker。mWorker会间接记录进mFuture。当mFuture被回调时,系统会间接回调mWorker的call()成员函数,而这个call()函数是整个AsyncTask的核心行为。
现在我们可以画一张AsyncTask的示意图:

其实,当一个AsyncTask被安插进线程池时,线程池主要关心的是其mFuture成员引用的FutureTask。所以我们可以画出如下示意图:

当回调发生时,最终间接执行到mWorker成员的call()函数,在介绍AsyncTask的构造函数时,我们已经见过该函数的代码,现在再列一遍:
1 | mWorker = new WorkerRunnable<Params, Result>() { |
看到了吗,当线程池里的某个线程回调到上面的call()函数时,会先把线程优先级设置为“后台线程”,然后会调用doInBackground()函数。大家还记得吧,前文说过我们在实现一个AsyncTask派生类时,主要重写的就是这个doInBackground()函数,现在终于派上用场了。
上面代码中还调用了一个不常见的函数:Binder.flushPendingCommands()。这个函数对应的注释是这样说的:(本函数)会将所有在当前线程里挂起的“Binder命令”扔回内核驱动。一般可以在执行那些有可能阻塞较长时间的操作之前调用一下该函数,这样可以确保挂起的对象引用被及时释放,避免“持有执行对象的进程”占据比“实际需要持有的时间”更长的时间。这部分说明让人有点儿迷惑,或许此处的调用仅仅只是为了在doInBackground()之后做一些binder驱动层的清理动作。
UI线程和AsyncTask工作线程之间的协作
回调的call()函数最终还会通过postResult(),发回一条MESSAGE_POST_RESULT消息。postResult()的代码如下:
1 | private Result postResult(Result result) { |
此处的getHandler()得到的实际是一个可向UI线程发送消息的handler(即AsyncTask的静态成员sHandler)。getHandler()的代码如下:
1 | private static Handler getHandler() { |
这里搞了个类似单例的sHandler,类型为InternalHandler:
1 | private static class InternalHandler extends Handler { |
从InternalHandler的构造函数可以看到,postResult()最终就是向UI线程发回MESSAGE_POST_RESULT消息的。
当UI线程最终处理MESSAGE_POST_RESUTL消息时,会调用AsyncTask的finish()。
1 | private void finish(Result result) { |
另一方面,用户在编写doInBackground()时,还可以在合适时机调用publishProgress(),向UI线程发出MESSAGE_POST_PROGRESS消息。publishProgress()的代码如下:
1 | @WorkerThread |
这个消息同样被刚刚说到的InternalHandler处理,处理时会回调AsyncTask的onProgressUpdate()。
关于UI线程和执行AsyncTask的线程之间的交互,我们可以画一张示意图如下:

这张图反映了一个AsyncTask对象在运作时,大体上是如何被UI线程和工作线程调用执行的。
AsyncTask的内部状态
细心的读者还会发现,AsyncTask在finish()时会把自己的状态置为Status.FINISHED。简单说来,AsyncTask可以处于3种状态,分别是PENDING、RUNNING、FINISHED。这3种状态的切换很简单,示意图如下:

cancel动作
当然,用户还可以随时中途放弃执行当前任务。不管是在主线程处理MESSAGE_POST_PROGRESS时,还是在工作线程处理doInBackground()时,用户都可以调用cancel()函数。该函数的代码如下:frameworks/base/core/java/android/os/AsyncTask.java
1 | public final boolean cancel(boolean mayInterruptIfRunning) { |
java/util/concurrent/FutureTask.java
1 | public boolean cancel(boolean mayInterruptIfRunning) { |
java/util/concurrent/FutureTask.java
1 | boolean innerCancel(boolean mayInterruptIfRunning) { |
简单地说,cancel()动作会将mCancelled设为true,这样以后再调用isCancelled()时,就会返回true。前文我们已经看过AsyncTask的finish()的代码,现在再列一下:
1 | private void finish(Result result) { |
可以看到,如果该任务是被用户cancel的,那么finish时执行的会是onCancelled(),而不是onPostExecute()。另外,为了确保在用户cancel任务之后,该任务能真的快速退出,我们应该在doInBackground()里周期性地检查一下isCancelled()的返回值,一旦发现,就立即退出。
小结
关于AsyncTask的知识,我们就先说这么多。现在大体总结一下:
- 使用AsyncTask时,主要是重写其派生类的doInBackground(),而且该函数会在线程池的某个工作线程里被回调的;
- 必须在UI线程调用AsyncTask的execute()或executeOnExecutor();
- 可以在doInBackground()里的合适时机调用publishProgress(),向UI线程通知工作进展;
- 可以随时调用cancel(),放弃执行任务。
注意点
- AsyncTask不与任何组件绑定生命周期。在Activity或 Fragment中使用 AsyncTask时,最好在Activity或 Fragment的 onDestory()调用 cancel(boolean);
- 若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露。AsyncTask应被声明为Activity的静态内部类;
- 当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作,在Activity恢复时的对应方法 重启 任务线程。