写在前面
感觉最近自己需要多读书,所以在以后的一段时间里可能都是笔记形式的文了,希望自己能厚积薄发吧。
AsyncTask简介
AsyncTask是一个轻量级的异步任务类,允许你将一个耗时操作放在后台进行,并且会返回操作的结果给你。那么AsyncTask和Thread-Handler或者线程池有什么异同呢?
在AsyncTask的源码注释里这样描述:
1 | /** |
AsyncTask能让更加简便的使用UI线程。这个类允许执行后台操作和将结果发送到UI线程而不必操作线程和handlers。
读了上面的注释让我们对AsyncTask有了一定的了解,这是个方便我们的类,让我们在后台执行操作结果而不必自己手动的去切换线程,那么这个类是否有其他的限制呢?
1 | /** <p>AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} |
AsyncTask被设计作为Thread和Handler间的帮助类,并非构建线程框架的通用类。AsyncTask理想情况下应该被用来进行短时间的操作,如果你需要保证线程长期运行,那么强烈推荐你使用java.util.concurrentpackage提供的多种API,例如Executor、ThreadPoolExecutor和FutureTask。
简单的演示
上面简单的介绍了一下AsyncTask,下面看一下如何使用。AsyncTask提供了4个核心方法:
onPreExecute(),在主线程中执行,在后台任务执行之前,此方法会被调用
doInBackground(Params… params),此方法用于执行需要执行的异步任务
onProgressUpdata(Progress… values),在主线程中执行,当后台任务的执行进度发生改变时此方法会被调用
onPostExecute(Result result),在主线程中执行,返回操作结果,返回类型是doInBackground的返回值
1 | new AsyncTask<String, Integer, Bean>() { |
上面的代码只是一个简单的实例,并非真的演示如何使用。以上代码中的三个参数可以理解为url、进度和自定义的数据类型。带入我们平时的开发中就是根据url拿到数据,然后在界面上更新进度。在方法中这样String… params表示不定数量的参数,是数组型的参数。上面的方法实际上运行的效果是串行执行的,你可能会说AsyncTask内部不是封装了一个线程池吗?为毛会是串行的?这个问题先留着,先把结论摆在这,而且我也在AsyncTask的源码中看到了如下的注释(原文不放了,感兴趣的请自己去看):
调度任务是用队列单独的调度一个后台线程还是用线程池取决于平台版本。刚发布的时候,AsyncTask是以串行线程的方式执行的。从Android DONUT(1.6)开始允许多任务并发执行。在Android HONEYCOMB(3.0)之后又变成了单任务串行执行,这是为了避免由于并发操作可能带来的错误。如果你真的想要并发执行,你可以使用excuteOnExecutor和THREAD_POOL_EXECUTOR。
好了,读到这,终于对AsyncTask有了一些了解了,带着一些问题去看看源码吧。
源码笔记
读源码先从AsyncTask的入口execute()开始看:
1 | public final AsyncTask<Params, Progress, Result> execute(Params... params) { |
调了一个方法,没啥好说的,跟进去看就行了,这里返回值是AsyncTask,方便我们持有一个AsyncTask的引用。
1 | public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, |
这里先看mStatus,这东西是个枚举类型,里面仨值分别是:
- PENDING:任务还没有被执行过,表示可以被执行
- RUNNING:任务正在执行
- FINISHED:任务已经完成
从代码中可以看出来只有这个值是PENDING的时候才会被执行,其他的值都会报异常,这就是AsyncTask对象只能运行一次的由来了,每次执行任务都需要新建一个AsyncTask对象(准确的来说是子类对象)。
在运行之后将mStatus的值改为RUNNING,之后这个对象就不能在其他的地方被执行了。可以看到在任务真正被执行之前调用了onPreExecute()方法,这就是这个方法可以做一些准备工作的原因。
之后先获取参数,再执行。这里先简单的说说,要弄懂最后这句exec.execute(mFuture);代码还需要结合前面的代码来看。
之前在前面说了AsyncTask内部有两个线程池,那么他要干啥为毛要两个线程池呢?因为一个线程池是串行的线程池,一个进程中的所有待执行的任务都会在这个串行的线程池中排队执行。接下来看一下AsyncTask内部的俩线程池在代码里长啥样:
1 | /** |
上面那个线程池是真正用来执行任务的,下面的是用来排队等待的。可以清楚的看到这俩是静态的,所以是全局共享这个就不做过多的解释了。那么按顺序来,从下面的线程池的execute()来看:
1 | private static class SerialExecutor implements Executor { |
从上面代码的流程我们可以认识到在我们不指定线程池的情况下,的确,我们的代码是以串行的方式被执行的。关于mFuture其真实类型是FutureTask,对此我们不需要再做更多的了解(其实我了解的也不多…),当然了如果你对Java的并发编程感兴趣可以自己去做更多的了解。在这我们需要知道的就是mFuture的run方法会调用mWorker的call方法,因此mWorker的call方法最终会在线程池中执行。
1 | mWorker = new WorkerRunnable<Params, Result>() { |
以上可以看到我们希望在后台执行的代码被调用了,并且结果被postResult这个方法发送了,跟进去看看:
1 | private Result postResult(Result result) { |
直接看这个消息在Handler里面是怎么处理的:
1 | private static class InternalHandler extends Handler { |
在这可以看到这个Handler是一个静态类,静态类会在类被加载的时候就被初始化,而Handler的初始化时需要looper()的,所以这就需要你在主线程中使用AsyncTask。否则要么出错,要么AsyncTask就被你废了。好了,继续看,在对应的情况底下调用了AsyncTask的finish方法,看下是啥:
1 | private void finish(Result result) { |
如果被取消,就调取消的方法,不然的话就回调这个onPostExecute(result),由于是经过handler发送的,所以线程已经切换到了AsyncTask调用的线程中去了(关于这个如果你有不明白可以看我的Android消息机制浅析),我们就可以在主线程中开心的使用这个结果去更新UI了。
小结
- AsyncTask用起来比较方便,但是特别耗时的操作并不适合用它来执行。
- AsyncTask默认是串行执行,但是你可以通过指定执行的线程池来让他并发执行。
- AsyncTask对象只能被执行一次。
- AsyncTask使用Handler来切换线程。
- AsyncTask一般情况下都是需要在主线程被实现和调用的。
- AsyncTask在不同版本的Android上可能会有不同的表现,但是现在用户Android版本普遍在4.0以上,这个就无需考虑了。
参考资料:
《Android开发艺术探索》
源码版本:Android 7.0 api 24