Android – AsyncTask与Handler 的关系

前面的文章已经分析了HandlerAsyncTask的原理,现在说说他们的异同点:

1. 相同点:AsyncTask就是封装了Thread+Handler,来简单实现做异步任务同时又能更新UI

2. 不同点: 在android 3.0之后的AsyncTask中的任务默认是串行执行的,如果你有多个异步任务要并发执行,应该使用Thread(Pool)来替代。 当然AsyncTask也是考虑到了这一点,所以提供了一个executeOnExcutor方法,可以传入我们自定义的executor来进行并发执行。AsyncTask内部实现的Executor是SerialExecutor, 是串行执行的,来看代码他是如何串行化的:1

调用execute的时候,会先把异步任务封装为一个runnable放到一个队列里面,然后再判断要不要执行该任务。
判断的依据很简单,如果当前已经有任务在跑了,那么就不跑了。那么,这个任务会被丢弃吗?不会!当前任务执行后的finally块里面会执行下一个任务。这里的mActive变量设计的很巧妙,当mTasks.poll()得到的下一个任务为空的时候, 就不会再往下执行了, 所以可以保证所有任务都能被执行到,而且任务都是串行执行的。这里execute和scheduleNext两个方法都加上了synchronized关键字,所以也不会有线程安全的问题。
看下AsyncTask的execute方法:
2
这里用的是默认的Executor, 而这个默认的Executor就是SerialExecutor, 而且是单例的
3
看到sDefaultExecutor前面有个volatile关键字就说明了这个Executor是可以被更改的,果不其然, AsyncTask提供了这么一个方法:
4
这样一来,我们通过更改默认的Executor就能达到AsyncTask里面的任务并发执行的目的。
另外,回到AsyncTask的execute方法,是通过executeOnExecutor来提交任务的,而恰巧,executeOnExecutor这个方法是public的,说明我们也可以通过executeOnExecutor这个方法来指定我们自定义的Executor来执行任务,从而达到并发执行的目的。
5
同时,AsyncTask也定义了一个Executor常量供我们使用(实际上他的SerialExecutor也在用),就是
6
所以我们调用AsyncTask.executeOnExecutor的时候,可以把AsyncTask.THREAD_POOL_EXECUTOR作为第一个参数传过去即可,也省去了我们自己定义(如果实在需要还是真的要自己定义)的代码了。
如果用Thread+Handler来实现类似AsyncTask类似的功能,可以用Handler的post方法,在Thread中处理任务的过程中,如果想要更新UI线程,有几种方法,一种是post,一种是sendMessage。如果从用法上来看,post应该说更实用一点,sendMessage的话你还要去封装一个消息,然后接受到消息的时候还要再把数据拿出来,进行UI的更新,例如:
7
3. 最后画一幅图来总结一下AsyncTask和Handler之间的关系:
9
绿色部分是Android框架定义的方法,我们无需去重载,而粉色部分的方法我们可以去重载来实现自己的业务逻辑。
注意:在doInBackground被调用之前会有一个线程池的调度过程,以及会先执行onPreExecute这个方法,这里省略了。

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