Android开发:AsyncTask源代码完全解析

从事Java开发以来,接触过很多的开源代码,自己能够明白代码但是想要表达出来却有点困难,从今天开始,逐渐开始对一些开源代码进行解析并记录成blog分享出来,希望以此提升自己的表达能力,如果文章中有什么出错之处,欢迎读者在评论中指出方便我及时的修正,以免误导其他读者,如果你有什么更好的建议也欢迎在下方留下你的评论,本人不胜荣幸。转载此文的朋友请带上原文的链接http://blog.csdn.net/d_clock/article/details/43805779好,扯淡的话到此为止,接下来进入正题。
作为一名Android开发者,一定对AsyncTask这个异步操作类不会陌生。这是我从大二开始接触安卓开发以来,最具争议性的一个类了,我看到网上很多人都在吐槽AsyncTask的性能多差,并发性多不行等等,在我所阅读过的开源框架的代码中很多异步操作也基本不用AsyncTask,都是采用Java的线程池操作,所以以至于有一段时间我也是从来不使用AsyncTask,认为AsyncTask的弊端很多,后来在公司里的一位大神说了一句“你没发现,AsyncTask已经封装好了异步线程和UI线程之间的交互了吗?Google提供这个类出来给开发者使用自然有他的道理”,我仔细一想,大神说的也挺有道理,回想之前看到的框架基本采用线程池来做异步操作,基本都是因为这些框架里面都没有涉及到与UI之间的交互,所以完全可以直接使用线程池来完成。于是便花一些时间好好琢磨一下AsyncTask的源代码(其实这代码已经开过好多遍了,每次看总是有新的体会),又瞎扯了一段内容。
日常开发中使用AsyncTask方式就像官方文档提供出来的这段Demo Code一样,首先继承AsyncTask类实现相应的泛型参数,并重写实现doInBackground抽象方法,这个方法就是用来执行我们需要的异步操作的。
技术分享
如何使用AsyncTask来做异步操作呢,很简单,同样来一段官方的Demo Code
技术分享
只需要new出这个AsyncTask并调用execute参入相应参数,即可以完成相应的异步操作流程,并最后将结果返回onPostExecute所处的主线程中进行处理,调用起来确实非常的方便就实现了和两个线程之间的交互。到此,我们已经重温了一边AsyncTask的基本使用,如果身为Android程序员你还不熟悉的话,想必应该去厕所面壁思过了。最后漏了补充一下,在使用AsyncTask编程时候需要注意的一些地方

  1. AsyncTask对象必须在UI线程创建
  2. execute方法必须在UI线程调用
  3. 不要在你的程序中去直接调用onPreExecute(), onPostExecute, doInBackground,
    onProgressUpdate方法
  4. 一个AsyncTask对象只能执行一次,即只能调用一次execute方法,否则会报运行时异常
  5. AsyncTask不是被设计为处理耗时操作的,耗时上限为几秒钟,如果要做长耗时操作,强烈建议你使用Executor,ThreadPoolExecutor以及FutureTask

接下来开始围观AsyncTask源代码,首先围观一下构造函数和一些相关的变量
技术分享
CPU_COUNT:CPU的核数
CORE_POOL_SIZE :最线程池并发线程数,直接和CPU核数挂钩
MAXIMUM_POOL_SIZE:线程池中的最大线程数,直接和CPU核数挂钩
KEEP_ALIVE:线程池中线程的最大存活时间
sThreadFactory:用于构造线程池的工厂,重写了newThread方法为每个线程做了命名,方便调试跟踪
sPoolWorkQueue:线程池中的工作队列,最大容量128的链式阻塞队列
THREAD_POOL_EXECUTOR:多任务并发工作线程池,初始化参数为以上的各大参数
SERIAL_EXECUTOR:串行工作线程池,实现类为下面的SerialExecutor
技术分享
sHandler:AsyncTask类的中用于线程交互的Handler,实现类为内部类InternalHandler(AsyncTask的实现就是将Thread和Handler进行封装来达到交互,省去我们变成时候自己封装的过程)
MESSAGE_POST_RESULT:sHandler返回结果的消息类型
MESSAGE_POST_PROGRESS:sHandler返回进度的消息类型
sDefaultExecutor:AsyncTask类的默认线程池,默认使用SERIAL_EXECUTOR串行线程池进行初始化
mWorker:实现Callable接口的类,在构造函数中初始化
mFuture:FutureTask实例,和mWorker搭配使用,同样在下面的构造函数中初始化
mStatus:标记当前AsyncTask的运行状态,有PENDING, RUNNING, FINISHED三种分别用于表示等待运行,运行中和运行结束这三种状态
mCancelled:运行任务被取消的标记位
mTaskInvoked:任务已经被调度运行的标记位
技术分享
到此,我们已经粗略的窥探完一些基础部分的代码,先了解上面这些才能够方便理解接下来继续围观的代码流程,我们知道,要执行一个AsyncTask,只需要在new出来后,调用execute方法就开始了一个异步任务,接下来就从execute的代码开始下手,官方文档上显示,提供的execute方法有两个,我们平常经常调用的是下面红色框框中的那个
技术分享
接下来就跟进这里的代码,接着进行围观
技术分享
大家可以看到execute函数中指调用了executeOnExecutor函数,并将其结果返回而已,而executeOnExecutor究竟用来干啥,从名字上看,顾名思义应该是在线程池中执行某个线程任务,他传入的参数有两个,除了execute本身传入的变长参数以外,就是AsyncTask类的静态变量sDefaultExecutor,这个静态的线程池我已经在前面介绍过了,默认是一个单任务串行的线程池,既然调用了executeOnExecutor那接下就围观executeOnExecutor里面的代码
技术分享
从这个函数的实现上看,进入这个函数后,首先就开始判断当前执行的AsyncTask的状态,如果是PENDDING,说明当前的AsyncTask从未执行过,则直接把mStatus状态标记为RUNNING状态,如果AsyncTask已经执行过execute方法,则此处的mStatus将不会再是PENDDING状态将直接抛出异常,这就可以很好的解释清楚为什么一个AsyncTask不能调用两次execute
技术分享
因为第一次调用的时候已经将PENDDING状态置为其他状态,再次判断到位非PENDDING状态调用就直接抛IllegalStateException了。
将mStatus状态进行设置后,接下来就会调用onPreExecute方法,
技术分享
这个方法实际上就是一个空方法,由于调用次方的时依旧处于UI线程中,所以AsyncTask的子类可以重写此方法并在里面实现一些UI操作,这也是AsyncTask设计的时候留下来的一个扩展方法,可以用来做异步操作开始前的一些预备工作。
技术分享
紧跟着,就开始将从execute传入的变长参数,存放到mWorker的成员变量中,并使用默认串行线程池来进行并发操作
技术分享
到此,executeOnExecutor函数的讲解基本完成,接下来继续跟入到exec的execute代码中,又需要重新的回到了SerialExecutor中的代码和AsyncTask的构造函数中去,在此借机浏览mWorker类的实现代码
技术分享
可以看到,WorkerRunnable实际上是一个带有mParams成员变量的一个抽象静态内部类而且继承了Callable,mWorker真是此类的实现实例,在executeOnExecutor中可以见到,mWorker中的成员变量mParams正是用来存放我们在execute调用的时候传入的参数。
技术分享
在构造函数中可以看到mWorker被实例化并实现了Callable接口中的call函数(这个函数就是和线程池中执行Runnable接口中的run函数一样,只不过Callable可以返回结果状态),进入执行call函数中的代码,首先现将标记当前任务被调用执行的标记位设置为true,接下来设置好线程的执行优先级,就开始就开始进入我们一直最常见的doInBackground函数里面了,并将参数传入,怎样,有木有一步一步的更加明白调用的过程,在doInBackground被执行完了之后就会将执行结果返回作为调用postResult函数的参数,在postResult函数中你会见到我们更见熟悉的Handler,由它将doInBackground中的执行结果返回给我们的UI线程的消息队列中,最终由Handler中handleMessage来完成调用AsyncTask中finish方法,而finish方法中最终会判断doInBackground是正常执行完了,还是中途被取消掉,由此最终来确定是调用onCancelled还是onPostExecute,最后将AsyncTask的执行状态设置为FINISH来完成我们整个异步线程和UI线程之间的交互过程。这里还有另外一个地方需要注意一下,就是mFeture中的done方法,主要是用于处理execute的AsyncTask没有被执行的情况。
技术分享
技术分享
技术分享
需要注意的是postResult中发送出去的消息类型是MESSAGE_POST_RESULT,这种消息的类型是表示着doInBackground被执行完的情况,但是你可以看到上面的InternalHandler类中,其实处理的消息有两种,这两种消息也在前面介绍成员变量的时候也提到过,另外一种消息的类型主要是用于向UI线程更新异步任务中执行任务的进度用的,例如我们最经常接触到的更新下载进度的例子,实际就是在doInBackground方法中不断的去调用下面的publishProgress方法,所以你也可以很清楚的看到,实际上也是在使用Handler发送MESSAGE_POST_PROGRESS类型的消息,最后在onProgressUpdate方法中进行相应的更新操作
技术分享
技术分享
而onProgressUpdate方法你也可以看到实际上也是AsyncTask留给我们扩展重写的方法
技术分享
到此,我们已经基本了解了整个AsyncTask的整个工作的过程,接下来我们再接着看看AsyncTask执行异步的并发性。这回先不急着看代码,先瞧瞧Google提供的API文档接口里面的一些东西。
技术分享
技术分享
AsyncTask提供了两个类型的线程池,前面已经介绍过串行工作线程池和并发工作线程池,而执行AsyncTask的函数也有两个,一个是execute,另一个是executeOnExecutor,我们上面的代码也见到了,实际上我们调用execute在代码中也是调用了executeOnExecutor函数,那么跟大家说这些和AsyncTask执行异步任务的并发性有神马关系呢?我们重新回到SerialExecutor中的代码里面去
技术分享
我们所有进行execute的AsyncTask,其本质都是最终被包装到一个FutureTask里面,然后放到一个Executor里面去执行,如果我们不另外指定这个Executor,那么默认就会用SerialExecutor来执行这些任务,SerialExecutor内部通过一个数组队列ArrayDeque来组织这些被执行任务,从代码中可以看到,每执行完一个任务,就会在ArrayDeque中删除,你只有在执行完了上一个Runnable任务的时候才可以接着执行下一个任务,如果前面的任务一直不执行完,那么后面任务永远也不会被执行到,这就很好解释了前面为什么说AsyncTask不适合用来做耗时很长的任务,因为一旦有一个任务耗时太长,在默认的AsyncTask执行机制下,后面的任务都将永远不会得到执行。如果你仔细的观察,也会看到执行AsyncTask的线程池都是静态的类变量,而不是成员变量,所以如果出现非常耗时的任务,将是涉及到app中所有的AsyncTask的问题。
Google在一开始设计AsyncTask的时候,主要是由于Android设备的配置当时还比较差,所以不适合太多的任务一起并发执行,考虑此些原因而将AsyncTask做成这种串行执行任务,不过随着硬件的发展,Google也考虑到AsyncTask所面临的多任务并发性问题,所以在Android3.0开始也扩展出了另外的THREAD_POOL_EXECUTOR并发线程池,但是从初始化THREAD_POOL_EXECUTOR的参数来看,Google仍然控制着AsyncTask同时执行任务的数目,并且直接跟CPU的核数挂钩,这样做也是在一定程度上考虑了性能的问题,Android3.0拓展出了executeOnExecutor接口,甚至可以通过executeOnExecutor接口在AsyncTask外部定义自己的并发线程池。到此我对AsyncTask的源代码解析已经差不多要进入尾声,最后想要再讲的一些主要就是AsyncTask的执行状态,以及取消正在执行的AsyncTask,关于这些主要可以看看下面的几段代码
技术分享
技术分享
技术分享
想必大家到此,已经熟悉了整个AsyncTask的基本操作和相关的执行流程。
最后我再做一下个人的啰嗦总结,从我个人目前的开发经验上来看,AsyncTask这个类确实已经做了很好的封装,已经将异步线程和UI线程之间的交互做了很好的处理,所以开发者可以从这Google提供的类上愉快的进行编程,如果考虑到并发任务数量的问题,也可以使用3.0之后提供的接口来提高并发操作的数量,但是我们仍需要好好的控制任务的最大并发数量,因为数量太大也会造成app的性能问题,这也是为什么Google提供的多任务线程池并发任务数目要直接和CPU核数挂上钩的原因,而追溯到AsyncTask的本质,我们从源代码中也可以一目了然的看出,它其实就是FutureTask和Handler的结合体。
关于AsyncTask的源代码解析到此结束,希望本文能够帮助你在Android开发中更好的了解和使用AsyncTask,为了更好的帮助大家理清思路,后续我会另外写一篇博客用UML图表示出AsyncTask的工作过程,这样将会显得更加直观尊重原创,转载请注明出处http://blog.csdn.net/d_clock/article/details/43805779

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。