AsyncTask详解
AsyncTask中的参数
AsyncTask是一个抽象类,如果我们想使用它就要自定义一个子类去继承它。继成时我们可以为
AsyncTask制定三个泛型参数:
- Params:调用AsyncTask的excute方法时传入的参数类型,如果该方法传入了多个不同类型的参数,那么就定义为Object。
- Progress:执行AsyncTask时如果需要在前台显示进度条,使用该类型作为进度的单位
- Result:当AsyncTask结束后,如果需要对结果进行返回,该类型作为返回值的类型
如果某一个类型不需要传递具体的参数,那么对应的泛型参数用Void
代替
AsyncTask中一些重要的方法
void onPreExecute():在主线程执行,用于进行一些界面上的初始化操作,eg:显示一个进度条对话框等
Result doInBackground(Params … params):在子线程中执行,用于执行耗时任务,相当于Thread中的run()方法
void onProgressUpdate(Progress… values):在主线程中执行,前提是在
doInBackground
方法中执行了publishProgress(Progress… values),由该方法自动调用onProgressUpdate
,可以界面上实时显示进度void onPostExecute(Result res),在子线程中执行,
doInBackground
方法执行结束后,将返回值传给该方法,负责任务结束后的工作void publishProgress(Progress… values),一般在
doInBackground
方法中调用(非必须),将当前进度传递出来,一旦执行了该方法,那么将自动回调onProgressUpdate方法,并将当前进度作为参数传递给它.void onCancelled(boolean cancel),在主线程中执行,如果被调用,那么
onPostExecute
方法不会被执行,详见源码解析.
AsyncTask的基本使用
一个简单的下载的例子
1 | public class AsyncTaskTest extends Activity |
AsyncTask的启动只需要执行:task.execute(Param … params);可以想其execute()方法中传入多个Param,代表多个任务,(eg:这个例子中可以传入多个url,那么就可以进行多个的下载),可以同时进行,其内部使用了线程池原理;
AsyncTask中的注意事项
- AsyncTask的类必须在主线程进行加载,也就是说第一次访问AsyncTask必须在主线程,在4.1之后的版本中,被系统自动完成,在5.0的代码中,在ActivityThread.main()中,调用了AsyncTask.init()方法来实现在主线程中被加载
- AsyncTask对象必须在主线程中创建
- execute()方法必须在UI线程中调用
- 不要在持续中自己调用onPreExecute(),doInBackground()等方法
- 一个AsyncTask对象执行一次execute()方法,多次执行会报错;
- AsyncTask在不同版本上的表现是不一样的,eg:1.6版本之前,串行执行任务;1.6时,采用线程池并行处理任务;3.0开始又采用一个线程来串行执行任务;
AsyncTask源码解析
这里分析的版本是Andoid4.0/4.2,不同的版本可能稍有不同;
首先从AsyncTask的execute()方法分析
1 | public final AsyncTask<Params, Progress, Result> execute(Params... params) { |
这里关注的点是:
- 最后将
mStatus
设置为RUNNING
,由此也可以得出AsyncTask只能执行一次,否则其execute()方法一进去判断状态,如果已经是RUNNING
,直接报错 - 执行了
onPreExecute()
方法,因此证明了onPreExecute()方法会第一个得到执行,当前依然在UI线程,所以我们可以在其中做一些准备工作。 - 调用了Executor的execute()方法,并将前面初始化的mFuture对象传了进去
- mWorker.mParams = params,将我们传入的参数赋值给了mWorker.mParams
- exec.execute(mFuture)
相信大家对与mWorker
和mFuture
感到困惑,我们找到这两个类
其在AsyncTask
中的定义private final WorkerRunnable<Params, Result> mWorker;
1 | private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> { |
可以看到是Callable的子类,且包含一个mParams
用于保存我们传入的参数,下面看初始化mWorker的代码
1 | public AsyncTask() { |
可以看到mWorker在构造方法中完成了初始化,并且因为是一个抽象类,在这里new了一个实现类,实现了call方法,call方法中设置mTaskInvoked=true,且最终调用doInBackground(mParams)方法,并返回Result值作为参数给postResult方法.这基本上就将 中几个重要函数的执行流程描述清楚了.
这里也出现了最重要的方法doInBackground()
,我们知道doInBackground()
的返回值是是作为参数传给onPostExecute(Result res)
,为什么是作为参数传给了postResult
呢?
其实可以想到:doInBackground()
方法的执行是在子线程的,而onPostExecute(Result res)
方法是在主线程的,这里直接传递是不可能的,需要一个中间的桥梁来实现线程的切换,这个桥梁就是postResult();
接着往下看,postResult
中具体做了什么
1 | private Result postResult(Result result) { |
果然,这里用到了handler
的消息传递机制,是在子线程中将结果传递出去,结合之前所说的AsyncTask类的加载必须在主线程,那么我们还可以料想到这个主线程中的AsyncTask在初始化时也创建了一个handler,并且重写了handleMessage()方法,在该方法中根据Message.What,来将返回的消息结果传给onPostExecute(Result res),这样整个消息就从子线程传递到主线程了.
是不是上面预想的那样,接着看代码,先看一下AsyncTaskResult
这个类里有什么,这是一个静态内部类,封装了当前的AsyncTask对象和要返回的结果集,其实这个结果集并没有什么用,因为最终取的还是第一个值 data[0].
1 | private static class AsyncTaskResult<Data> { |
接下来看一下AsyncTask中的handler对象在哪里?
1 | private static final InternalHandler sHandler = new InternalHandler(); //AsyncTask中的成员变量 |
我们先看正常发出的标签 msg.what是MESSAGE_POST_RESULT
,这里调用了result.mTask.finish(result.mData[0]);
1 | private void finish(Result result) { |
看到这里进行了判断,如果该任务被取消了,那么走 onCancelled(result)
的分支,否则,执行onPostExecute(result)
,最后将状态设置为最后将状态置为FINISHED
,整个任务就结束了.
mWoker看完了,应该到我们的mFuture了,我们在使用Future的时候,是将其作为参数传入Thread中的,依然实在构造方法中完成mFuture的初始化,将mWorker作为参数,复写了其done方法。done()方法是在其成员变量Callable中的call()方法执行结束之后才执行的回调,此时调用其get()方法不会阻塞主线程,
1 | public AsyncTask() { |
这里在任务执行结束后调用了postResultIfNotInvoked(get())
,看一下这个方法是如何实现的
1 | private void postResultIfNotInvoked(Result result) { |
如果mTaskInvoked
不为true
,则执行postResult
;但是在mWorker
初始化时就已经将mTaskInvoked
为true
,所以一般这个postResult()
执行不到。
小总结一下:这里是介绍了两个变量的初始化:分别是mWorker
以及mFuture
,这里mWorker
其实是一个Callable
,mFuture
其实是一个FutureTask
具体使用还请参考《疯狂Java讲义》中的关于Callable和Future的使用吧
好了,到了这里,已经介绍完了execute方法中出现了mWorker和mFurture,不过这里一直是初始化这两个对象的代码,并没有真正的执行。下面我们看真正调用执行的地方。
excute()方法
还记得上面的execute
中:exec.execute(mFuture),其中exec
为executeOnExecutor(sDefaultExecutor, params)
中的sDefaultExecutor
下面看这个sDefaultExecutor
1 | private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR; |
可以看到sDefaultExecutor其实为SerialExecutor的一个实例,(SerialExecutor实现了Executor接口,该接口中只有一个void execute(Runnable r)方法)其内部维持一个任务队列;直接看其execute(Runnable runnable)方法,将runnable放入mTasks队尾,但是这里提供的Runnable并不是AsyncTask在构造方法中创建的FutureTask(虽然FutureTask也是一个Runnable),而是自己new了一个Runnable对象,在其run的内部手动调用了FutureTask.run(),最后执行scheduleNext()方法.
这个时候就要说一下其实AsyncTask其实是串行执行任务的,我们向ArrayDeque<Runnable> mTasks
中添加一个任务(注意这里只是添加并不是执行…),添加完后都会判断mActivie是不是为null,如果此时没有任务在执行,那么就会调用scheduleNext()方法,但是此时不为null的话,就什么也不做,仅仅是添加任务,而当scheduleNext()中执行完一个任务后,其finally中会再次调用scheduleNext()方法,执行下一个任务,这就做到了串行…
问题:既然AsyncTask只能execute一次,要线程池干什么?
两个原因:
(一)同一个AsyncTask只能execute()一次,如果代码是
1 | new AsyncTask().execute(); |
那么就会有多个任务了,注意:AsyncTask中的线程池是是static final
的,这样就会产生多任务了,那么线程池自然也就派上用场了
(二)在3.0之前的版本,如果提交了多个任务,那么其线程池不是串行执行的,而是并行执行的.
问题:在3.0之后可以依旧使用任务并发执行吗?
可以,只不过不要调用new AsyncTask().execute();
而是调用new AsyncTask().executeOnExecutor(Executor exec,Params... params)
,这时候,我们就可以不使用默认的sDefaultExecutor了,而是我们自己提供一个线程池,实现并发.
我们知道在execute()方法中其实是调用了executeOnExecutor(),而这个方法在3.0之前是没有的,3.0之前直接把execute(),然后线程池就并发执行了,当然了,线程池你也知道,如果你提交的任务数过多,就直接报错的,这也是3.0之前的一个缺点,不可配置.