AsyncTask异步任务 源码解读

之前我们介绍了Handler的一些基本用法,也解读了Handler的源码。通过Handler我们可以简便的切换到主线程进行UI操作。而AsyncTask的出现使我们不用去关心线程管理和切换的一些细节,我们可以更轻松的去操作UI。

基本概念

AsyncTask异步任务的作用

AsyncTask,见名之意,异步任务。允许我们在后台做一些耗时操作,然后切换到主线程更新,而且这一过程变得非常简便。一提到异步任务,我们的第一反应就是多线程。假如我们现在需要去下载一张图片,然后在界面上显示,如果没有AsyncTask,我们的异步操作可能就会这么写:

  • 普通写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    new Thread(new Runnable() {
    @Override
    public void run() {
    Bitmap bitmap=loadBitmap(url);//loadBitmap是封装好的工具类,用来下载图片,返回bitmap
    mHandler.post(new Runnable() {
    @Override
    public void run() {
    mImageView.setImageBitmap(bitmap);
    }
    });
    }
    }).start();

那么,AsyncTask怎么写呢?

  • AsyncTask写法
    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //AsyncTask下载并展示图片
    new MyAsyncTask(mImageView).execute(url);

    //MyAsyncTask的实现
    public class MyAsyncTask extends AsyncTask<String,Void,Bitmap>{
    private ImageView mImageView;
    private ProgressDialog mProgressDialog;
    public MyAsyncTask(ImageView imageView){
    this.mImageView=imageView;

    }
    @Override
    protected void onPreExecute() {
    super.onPreExecute();
    mProgressDialog=new ProgressDialog(mImageView.getContext());
    mProgressDialog.setMessage("正在下载");
    mProgressDialog.show();

    }

    @Override
    protected Bitmap doInBackground(String... params) {
    retrun loadBitmap(params[0])
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {

    super.onPostExecute(bitmap);
    if(bitmap!=null){
    mImageView.setImageBitmap(bitmap);
    }

    }
    }

可以看出,AsyncTask的结构要清晰很多。有准备操作的方法onPreExecute,有专门执行后台操作的方法doInBackground,也有更新UI的方法onPostExecute,甚至还有个更新进度的方法onProgressUpdate,每个方法各司其职。
你可能会想,除了结构清晰,也没看出有其他什么优势啊。那么,我们现在来想象一个场景——列表滑动。假如我们在ListView中去请求图片,直接new线程显然不太合适,快速滑动会开启大量的线程,线程阻塞不说,还会消耗大量系统资源。而AsyncTask由于内部管理着一个线程池,就可以解决这些问题。

AsyncTask的工作流程

AsyncTask不仅使用起来非常简便,此外,工作流程也非常清晰。

  1. doInBackground编写耗时任务。
  2. 在主线程提交任务到线程池
  3. 线程池进行处理任务
  4. 处理完切换到主线程并返回结果。

AsyncTask异步任务疑问

在了解过AsyncTask的一些用法后,我们不难会产生一些疑问。

  • AsyncTask的线程切换是怎么完成的?
  • AsyncTask的execute方法的可变参数怎么用?
  • execute方法为什么只能调用一次?
  • execute方法为什么只能在主线程调用?
  • AsyncTask是串行执行还是并行执行?

让我们带着疑问,去源码里探索吧。

初识API

ThreadPoolExecutor(线程池执行者)

一看名字就知道,线程池,JAVA线程管理的一个api。通过下面的构造方法就可以建立一个线程池。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  /**
* @param corePoolSize 核心线程数,即使空闲也一直存在,除非给核心线程也设了超时时间。
* @param maximumPoolSize 线程池的最大线程数
* @param keepAliveTime 除核心线程外的其他线程等待超时时间,超时就会杀死。
* @param unit 时间单位
* @param workQueue 工作队列,多余的任务就会放在这个队列中,等待被执行。
* @param threadFactory 线程工厂,用来创建线程
* @param handler 当工作队列满后,再添加任务时的处理策略,处理策略有如下几种,DiscardOldestPolicy:挤掉最旧的任务。DiscardPolicy:废弃当前被添加的任务,CallerRunsPolicy:直接在添加任务的那个线程执行,AbortPolicy:直接抛异常。默认的处理策略时AbortPolicy。
*/

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

我们常用的Executors.newFixedThreadPool(int nThreads)等创建线程池的方法,其内部也是调用了这个构造方法。

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

Queue和Deque(队列和双端队列)

Queue队列,继承自集合(Collection),通常遵循着先进先出原则(FIFO)(除了优先级队列和后进先出队列)对应的api如下。

操作 抛出异常(已满或已空) 返回空值(已满或已空)
添加元素 add(e) offer(e)
取出一个元素 remove() poll()
查看一个元素 element() peek()

我们一般采用offer,poll,peek方法来操作元素,以免抛出异常。

Deque双端队列,继承自Queue,支持双端移除和插入。因继承自Queue,所以可以直接当作Queue队列来用,此时遵守FIFO原则,当然,可以显示指明操作队头还是队尾offerFirst(e),pollFirst(),offerLast(e),pollLast()..
Deque的api对照如下

Queue方法 同等的Deque方法
add(e) addLast(e)
remove() removeFirst()
element() getFirst()
offer(e) offerLast(e)
poll() pollFirst()
peek() peekFirst()

Runable,Callable,Future,FutureTask四者的关系。

  • Runable: 一个接口,常用于执行异步任务,有一个抽象方法void run(),该方法没有返回值。

    1
    2
    3
    public interface Runnable {
    public void run();
    }
  • Callable: 一个接口,常用于执行异步任务,有一个抽象方法V call(),该方法含有返回值。

    1
    2
    3
    public interface Callable<V> {
    V call() throws Exception;
    }
  • Future: Runable和Callable就像脱缰的野马,提交给线程就没法进行管理了。而Future则是用来管理他们的,可以取消任务,获取结果等。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException;

    }
  • FutureTask是Future的一个实现类。用来包装Runable和Callable,使其可控制。常规用法如下。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //创建一个Callable对象,在实现方法中写耗时操作
    Callable callable= new Callable<Bitmap>() {
    @Override
    public Bitmap call() throws Exception {
    return loadBitmap(url);
    }
    };
    //使用FutureTask包装Callable,使其可控制
    FutureTask<Bitmap> futureTask=new FutureTask<Bitmap>(callable);

    //提交到线程中
    new Thread(futureTask).start();

    //阻塞直到获取结果,如果不想阻塞,就重写done方法,在里面get()
    Bitmap bitmap= futureTask.get();

工作原理

编写任务(重写doInBackground方法)

这一步比较简单,就是在doInBackground里面写一些耗时操作,比如网络访问。这里不作演示。

提交任务到线程池(调用execute方法)

AsyncTask的使用比较简便,一般都是以new MyAsyncTask(..).execute(url)的形式调用。那么,execute到底做了什么呢?在介绍之前,我们先来看一下AsyncTask的构造方法。

AsyncTask的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置任务已被调用的标识
//...
//省略了部分代码
Result result = doInBackground(mParams);//耗时操作
//...
//省略了部分代码
return postResult(result);
}
};

mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//...
//省略了部分代码
}
};
}

看完上面的代码,可能有点手足无措。不要慌张,慢慢来。
先来看看mWorker,里面的东西是不是非常眼熟,跟Callable的写法简直太像了。事实上,的确继承自Callable。而doInBackground只是Callable实现方法中的一部分。执行完过后调用了postResult来传递数据,postResult做了什么暂且不做研究,后面再做介绍。WorkerRunnable的代码如下,仅仅是定义了一个变量用于保存参数。

1
2
3
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}

再来看看mFuture,是不是也非常眼熟,包装了一下mWorker使其可控制。
看完上面两段源码后,有没有发现跟我上面举的FutureTask的例子有点像,那么,接下来要干嘛相信你已经很清楚了,没错,就是提交到线程或者线程池中去执行这段代码。只要被执行就能获得结果完成使命了。
真是万事俱备,只欠线程池啊。

调用execute方法

现在回过来看一下execute方法,看它到底做了什么事。

1
2
3
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}

可以看到内部经过了一次包装,且传入了一个默认的Executor,我们来看一下executeOnExecutor的源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}

我们可以发现在这个方法中直接调用了onPreExecute,而onPreExecute是用来干嘛的呢?做一些提交之前的操作,比如显示一个进度框等UI操作,这也就暗示了,如果要在onPreExecute中做UI操作,则必须在主线程中调用execute方法。此外我们也能隐隐约约知道为什么execute只能提交一次,因为,一旦提交过任务,就会将状态设置为Status.RUNNING,完成后就变为Status.FINISHED,按照源码逻辑可知,再也回不到Status.PENDING状态,所以不能再提交第二次,否则会抛异常。那么,为什么这么设计呢?是因为FutureTask的缘故,FutureTask的计算是不能被重新调用的(除非调用的是runAndReset())。
Status是一个枚举类,如下。

1
2
3
4
5
public enum Status {
PENDING,//还未调用
RUNNING,//调用中
FINISHED,//完成
}

继续阅读源码,发现了这一行exec.execute(mFuture);莫非就在这里提交给了线程池去执行?满怀期待的找到sDefaultExecutor的源码实现一看:

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
26
27
28
29
30
31
32
33
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;

public synchronized void execute(final Runnable r) {

//将Runable对象添加到双端队列里
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);
}
}
}

What?这个线程池不是用来执行任务的?心中一万只草泥马奔腾而过。没办法,只能继续阅读源码。发现了ArrayDeque双端队列,一看到队列我们的第一反应就是要排队。可是排队干嘛呢?干嘛不直接提交到线程池里面去执行。从源码可以看出使用了ArrayDeque双端队列来存放Runnable对象,你可能会疑问,不是FutureTask吗,怎么变成Runnable了,那是因为FutureTask也实现了Runnable接口,莫急,接下来就是排队的核心代码,为了使看起来更直观,修改成如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
new Runnable() {
public void run() {

try {
r.run();
} finally {

//取出一个任务添加到线程池里执行。
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
}

从上面的源码可以看出,使用了Runnable包装了原来的FutureTaskrun方法,巧妙的使用finally来保证任务的串行执行。
看到这里恍然大悟SerialExecutor原来是一个用来排队的线程池。THREAD_POOL_EXECUTOR才是我们一直在苦苦寻找的用于执行任务的线程池。exec.execute(mFuture)先把任务保存到排队线程池的队列中,然后串行提交到执行任务的线程池。也就是说,每执行完一个任务后,才会从队列中取出下一个任务到线程池中。
看到这里终于可以松一口气了,算是看到了提交到线程池的代码,第二步任务终于完成了。

线程池处理任务( THREAD_POOL_EXECUTOR.execute(..);

线程池的内部工作逻辑这里就不深究了,我们只需知道目前为止我们已经成功将任务提交到线程池中,只需等待处理,便可获得结果。我们来看看线程池本尊是怎么实现的。源码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//线程配置
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
//线程工厂
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);

public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};

//工作队列
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);

/**
*线程池的构造方法
*/

public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

线程池的实现是一些很常规的代码,没有什么可介绍的,唯一需要关心的问题就是线程池开多大合适。核心线程数为N+1,最大线程数为2N+1。

处理完毕,返回结果(回调onPostExecute

经过线程执行完毕,终于可以获取结果呢。还记得AsyncTask的构造方法吗?不记得也不要紧,我们再看一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置任务已被调用
//...
//省略了部分代码
Result result = doInBackground(mParams);//耗时操作
//...
//省略了部分代码
return postResult(result);
}
};
//...
//省略了部分代码
}

由源码可知,在处理完毕后会调用postResult,postResult的源码如下

1
2
3
4
5
6
7
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

AsyncTaskResult是一个包装类,里面保存了结果数据和AsyncTask对象。

1
2
3
4
5
6
7
8
9
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;

AsycTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}

此外,似乎又看到了熟悉的身影MessageHandler。看到这里终于明白了,内部通过Handler来进行切换线程,然后更新UI。如果我没猜错,在Handler的handleMessage方法内,一定直接或者间接的调用了onPostExecute
为了验证我的猜想是否正确,顺藤摸瓜找到了Handler的实现。

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
26
27
 private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}

//Handler的实现
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@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;
}
}
}

找到我们刚刚发出的消息类型MESSAGE_POST_RESULT。发现没有直接调用onPostExecute而是调用了AsyncTask的finish方法,继续追溯源码。

1
2
3
4
5
6
7
8
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}

看完源码才恍然大悟。原来有两种类型啊,一种是正常执行完毕的,回调onPostExecute,还有一种是被取消了,回调onCancelled。那么问题来了,怎么取消一个任务?
只需调用AsyncTask的cancel(boolean)方法即可。cancel会调用FutureTask的cancel方法去中断任务。

1
2
3
4
5
6
7
8
9
10
  //取消一个任务
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);//设置一个取消标识
return mFuture.cancel(mayInterruptIfRunning);//取消一个任务
}

//获取取消的状态
public final boolean isCancelled() {
return mCancelled.get();//获取取消标识
}

从源码可以看出,取消任务后是不会再走onPostExecute的,只走onCancelled方法。
看到这里。算是把AsyncTask的源码给看的差不多了。

最后

  • 怎么更新进度?更新进度在哪个线程进行?
    更新进度的源码如下。使用时,在doInBackground中进行调用。然后由Handler传递到主线程中,重写onProgressUpdate接受即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
    getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
    }


    private static class InternalHandler extends Handler {
    public InternalHandler() {
    super(Looper.getMainLooper());
    }
    @Override
    public void handleMessage(Message msg) {
    AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
    switch (msg.what) {
    //省略了部分源码
    case MESSAGE_POST_PROGRESS:
    result.mTask.onProgressUpdate(result.mData);
    break;
    }
    }
    }
  • AsyncTask的execute方法的可变参数怎么用?
    从源码来看,execute的可变参数全部传入进Result result = doInBackground(mParams);,也就是说,自己可以结合场景巧妙使用。可以传多个url,批量下载小文件等。

  • AsyncTask是串行执行还是并行执行?
    从以上源码分析(Android5.0)得出,是串行执行。但是官方api文档指出。Android1.6之前和Android3.0之后是串行执行。在这两个版本之间采用的是并行执行。于是找了份2.3的源码,果真如此,可以发现没有了排队线程池的踪影。源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //execute的源码如下
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    if (mStatus != Status.PENDING) {
    //..
    //省略了部分源码
    }

    mStatus = Status.RUNNING;

    onPreExecute();

    mWorker.mParams = params;
    sExecutor.execute(mFuture);

    return this;
    }

    //sExecutor的实现如下,可以看出直接就是一个任务线程池,没有用于排队的线程池。
    private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
    MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
  • Android3.0后AsyncTask怎么并行执行?
    其实很简单,想个办法跳过那个排队线程池即可。可以调用executeOnExecutor(Executor exec, Params... params),如上所示,直接传入一个线程池进行执行。

总结

经过解读了AsyncTask的源码,从侧面可以看出,Handler不可动摇的地位,毋容置疑。