Android – AsyncTask 源码分析

AsyncTask,看名字直接翻译就是异步任务的意思,顾名思义,是用来处理异步的任务的,那么什么任务需要异步处理呢,那些需要比较耗时的计算和资源获取都需要异步处理。如果不异步处理的话,处理这个任务的UI线程就会出现卡顿的情况。

1. AsyncTask是什么?
先看源码中的javadoc:
1
简单翻译一下: AsyncTask能够适当和简单地使用UI线程。可以处理后台操作以及发送操作结果到UI线程,而不需要操作Thread和Handler。AsyncTask就是设计用来简化Thread和Handler的使用的工具类,在做一些短操作的时候(最多几秒)应该观念性的想到AsyncTask。如果你需要保持线程在后台跑一段时间,那么强烈建议你使用JUC里面系统的一些并发相关的API,比如Executor,ThreadPoolExecutor和FutureTask等类。一个异步任务是由一个计算逻辑来定义的,跑在后台,在跑完之后将结果反馈给UI线程进行处理。定义一个异步任务需要三个泛型类型(Params,Progress和Result)以及4个步骤(onPreExecute,doInBackground,onProgressUpdate和onPostExecute)。
翻译太差,看得云里雾里有木有,其实简单的说就是异步任务(AsyncTask)是为了简化Thread和Handler的配合使用而定义的一种工具类,实现他,只要制定好几个参数的泛型类型以及覆盖几个步骤的方法(事件)就可以了。其底层还是通过线程(池)和Handler来实现的,后面会提到。

2.定义一个AsyncTask
可以通过匿名内部类的方式,也可以通过类继承的方式,定义一个AsyncTask,但是都必须实现doInBackground这个方法。(吐槽:这个方法的参数类型就是泛型定义里面的Params,但是为什么是不定数组,这个比较奇怪,不知道这个接口的设计者怎么想的。)后面再将实现机制的时候在说说这个方法要怎么实现。

3.实例化AsyncTask
new一个AsyncTask的时候,发生了什么?直接看他的构造方法就知道了:
2
这里mWorker就是一个Callable对象,执行的时候会调用到他的call方法,这里call方法会调用doInBackground方法,所以这个mWorker实际上就是把doInBackground做了封装,保证在执行的时候会调用到这个后台方法。而mFutrueTask就是吧mWorker再封装成FutureTask对象,在任务完成的时候执行postResult方法,把数据发给内部的Handler进行处理。这里内部的handler是在类加载的时候定义好的:
3
看看他的实现:
4
很简单, 根据发过来的消息类型(what),去执行对应的那个方法。注意这里有finish和onProgressUpdate两个路径,先看看onProgressUpdate是用来干嘛的:
我们在定义AsyncTask的时候,可以覆盖其onProgressUpdate方法,这个方法可以更新UI,而且不是等AsyncTask执行结束的时候, 那么是在什么时候触发呢,是在doInBackground中调用了publishProgress这个方法的时候,就会触发这个事件。这个可以干嘛用呢?最直接的一个例子就是进度条(下载,听歌播放等)。进度条一般是有一个进度的过程,不仅仅是开始和结束两个状态,所以我们处理了部分数据之后,为了更及时地反馈给用户,需要更新进度条的进度,所以需要在doInBackground里面调用 publishProgress方法。这就是为什么定义一个AsyncTask需要第三个泛型参数,这个参数就是为了进度中的数据传过来的。他的实现也很简单:
5
把当前的AsyncTask包装成AsyncTaskResult,加上当前进度的参数,发送到内部的handler去处理。
再回头来看handler中,对于消息的类型(what)是MESSAGE_POST_PROGRESS的消息会触发onProgressUpdate事件,从而实现线程的更新。
那么什么时候触发finish事件呢?很显然上面提到的mFutureTask的结束方法里面会postResult,而postResult就是给handler发送一个what等于MESSAGE_POST_RESULT的消息,这时就会触发finish事件(其实不是finish事件,后面会分析到)了。
所以这里几个参数是对应的
execute(Params...)
doInbackground(Parmas...)
publishProgress(Progress ...)
onProgressUpdate(Progress...)
finish(Result)
画个图来简单理解一下吧:
6
其中, 绿色的部分是我们不需要实现的, 粉色的部分是我们可以实现的一些事件。这里已经把整个AsyncTask的生命周期画出来了。

4.启动AsyncTask
调用AsyncTask的execute(Params...)方法就可以了, 这个方法是直接调用了内部的另一个方法,executeOnExecutor(Executor, Params...),这里传入的executor参数是AsyncTask内部自己实现的SerialExecutor,用法就是一个线程池。当然我们可以在外部直接调用这个executeOnExecutor方法,然后指定我们自己实现(or定义)的Executor就可以了。不过既然人家提供了,不用白不用你说是不?
7
看吧,这里会先触发onPreExecute事件,然后把参数交给mWorker定义的mParams,然后才丢到池里面进行处理。这个SerialExecutor实现也不复杂:
8
先把runnable对象放到队列里面(mTasks.off),然后再出列交给ThreadPoolExecutor处理,这的实现就是实现了异步任务的串行化处理,先来先执行。

5. 更新UI
在执行过程或者结果的时候需要更新UI,那么在哪里更新呢?不可能直接在doInBackground方法里面更新,因为这时候还不是在UI线程里面,只有在几个on*的事件方法内可以执行UI的更新,因为这几个事件确实是在UI线程中执行的。其中onPreExecute是在启动时还没有进入线程池之前的更新,其他几个都是通过handler来实现的。那么,我们在doInBackground中要如何更新UI呢?很简单,调用publishProgress方法就可以了,看他javadoc里面提供的代码:
9
调用publishProgress的时候把当前处理的进度(Progress的含义)传过去,然后在onProgressUpdate事件里面就可以使用这个进度的结果,然后更新UI.

6. 任务结束
任务结束,也会触发事件, 一种是正常结束, 会调用onPostExecute;另一种是用户自己取消(调用AsyncTask的cancel方法), 则会调用onCancelled事件.在结束这里我们也可以执行UI操作,比如结束进度条神马的, 你看这个AsyncTask简直就是为了类似进度条的UI的完美设计啊.
10

7.分析总结
最后来分析一下AsyncTask是如何搭建起Thread和Handler之间的桥梁的。首先,先从AsyncTask的入口方法,execute入手,他最终是提交任务给了本地的线程池去处理,这就打通了Thread的这一层。然后他提供了publishProgress这个API来发送消息到Handler里面,就打通了Handler的这一层,如此而为之的目的是为了具备线程的异步执行的特性同时又有更新UI的能力。如下图
11