基础了解
介绍
在Android中实现异步任务机制有两种方式:Handler和AsyncTask。
Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。
为了简化操作,Android提供了工具类.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务,但其内部也是使用Handler来传递消息,而且基于线程池。因此明显的AsyncTask比Handler要重量级。
功能总结:
- 能定义以下执行过程的操作:预执行、执行后台任务、执行进度反馈、执行完毕
- 对于以上功能,可以提供友好方便的API供开发人员调用
- 可以停止任务的执行
涉及的主要类
- AsyncTask
: 将要介绍的主角,这是一个抽象类 - Params:泛型类,是启动任务执行的输入参数类型
- Progress:泛型类,是后台任务完成的进度值的类型,如进度百分比
- Result:泛型类,是后台任务完成后的返回值类型
涉及的主要方法
- doInBackground(Object[] params):继承该类唯一必须要实现的方法,做一些耗时操作
- onPreExecute():耗时任务执行前的一些操作,运行在UI线程
- publishProgress ():在
doInBackground()
方法中进行调用,当这个方法被调用,会去执行onProgressUpdate()
方法 - onProgressUpdate(Object[] values):当
publishProgress()
被调用时执行,用来实现进度的更新,运行在UI线程 - onPostExecute(Object o):任务结束后的操作,运行在UI线程
- onCancelled():取消执行
如何使用
步骤
1、新建一个类MyAsyncTask
(随意命名),继承AsyncTask
。
2、实现其中的doInBackground()
方法,来进行耗时的操作,这个是必须实现的;然后,根据需要选择实现onPreExecute()
、onProgressUpdate()
、onPostExecute()
方法,它们的功能在前面已有介绍。
3、最后调用execute()
方法,来开启这个异步任务。
注意事项
- 异步任务的实例必须在UI线程中创建。
- execute(Params… params)方法必须在UI线程中调用。
- 不能在doInBackground(Params… params)中更改UI组件的信息。
- 一个任务实例只能执行一次,如果执行第二次将会抛出异常。
代码示例
对应上面第一、二步:
|
|
第三步:
|
|
源码分析
流程分析
在分析源码时,针对于这种框架层源码,我采用流程分析法,也就是从我们最先开始调用的方法开始分析,逐步探寻里面是如何调用其它对象和其它方法的。
还有一种方法是直接打开AsyncTask类,查看里面的方法、字段等,不过容易让人摸不着头脑,不容易搞清楚流程,所以这里我还是先找到一个入口,然后顺着这个入口去分析。
毫无疑问,对于AsyncTask,我们最先调用的就是它的execute()
方法,代码如下:
|
|
只有一行代码,就是调用了executeOnExecutor()
方法,我们进入这个方法:
|
|
mStatus
初始化是PENDING状态的,所以首先设置当前AsyncTask的状态为RUNNING。从上面的switch也可以看出,每个异步任务在完成前只能执行一次,否则报错。- 然后执行
onPreExecute()
,当前依然在UI线程,所以我们可以在其中做一些准备工作。 - 将我们传入的参数赋值给
mWorker.mParams
。 - 调用
exec.execute(mFuture)
。
前两步没什么可说的,很容易理解,重点是最后两步,我们先分析mWorker.mParams
,mWorker
在代码中的声明如下:
|
|
很明显,我们接下来要看看WorkerRunnable
这个类是如何定义的:
|
|
它是一个抽象类,实现了Callable接口,且包含一个mParams用于保存我们传入的参数,下面看一下它的初始化过程:
|
|
mWorker在构造方法中完成了初始化,可以看到在这里new了一个实现类,实现了call
方法,call
方法中设置mTaskInvoked=true
,且最终调用doInBackground(mParams)
方法,doInBackground(mParams)
由开发者来实现,然后返回Result值作为参数给postResult()
方法,下面进入postResult()
:(注意:这段代码是初始化,但是这个call()
方法并不是在这里调用的,不过我们这里先把这一段的代码逻辑给讲了,后面的讲解会和这里衔接上)
|
|
可以看到postResult中出现了我们熟悉的异步消息机制,AsyncTaskResult就是一个简单的携带参数的对象,最终通过postResult将结果投递给UI线程。
看到这一步,一定会想到在某处肯定存在一个Handler,且复写了其handleMessage方法等待消息的传入,以及消息的处理。所以我们进入getHandler()
方法进行查找,最终找到代码如下:
|
|
对于mWorker.mParams = params
方法就分析到这,这行代码的作用就是获得从UI现存传递来的参数,下面开始分析exec.execute(mFuture)
方法:
|
|
进入execute(mFuture)
发现这是一个接口,所以我们需要找到它的实现类:
|
|
找到了,可以看到它是一个SerialExecutor对象,我们直接看execute()
方法,它有两步:
第一步: 封装任务(目的是让任务串行),然后放入队列。新建一个Runable包裹提交进来的任务,在try作用域里面先执行r.run(),必须等任务执行完后(或者会执行失败也算是‘执行完’),要保证队列中的其他任务执行,所以在finally作用域中会执行 scheduleNext()调度下一个任务。所以任务是串行执行的。最后我们会把这个新建的Runnable对象放入队列mTasks中。
第二步:查看是否有可立即执行的任务mActive。 如果有执行的,则立即放入THREAD_POOL_EXECUTOR线程池调度器执行。
关于ThreadPoolExecutor这里不做介绍,下面直接进入到ThreadPoolExecutor的execute()
方法,如下 :
|
|
这段代码的主要功能是将异步任务mFuture加入到将要执行的队列中,重要的函数为addWoker()
,这里就不再进入讲解了。需要说明的是,这个线程池是一个静态变量;那么在同一个进程之内,所有地方使用到的AsyncTask默认构造函数构造出来的AsyncTask都使用的是同一个线程池。
如果App模块比较多并且不加控制的话,那么超过了工作队列以及线程数目的限制导致这个线程池发生阻塞,那么悲剧发生,默认的处理方式会直接抛出一个异常导致进程挂掉。假设你自己写一个异步图片加载的框架,然后用AsyncTask实现的话,当你快速滑动ListView的时候很容易发生这种异常,这也是为什么各大ImageLoader都是自己写线程池和Handlder的原因。
看到这里,我们发现接连使用了两个调度器,一个是SerialExecutor的execute()
方法,一个是ThreadPoolExecutor的execute()
,为什么要用两个调度器呢?
那是因为它们负责的功能不同,从我们上面对SerialExecutor的分析可以看出,SerialExecutor是为了保证任务被一个一个的取出执行,也就是说AsyncTask中任务是串行执行的,只有一个AsyncTask实例执行结束,才能去调度另一个。我们从SerialExecutor的实例是个静态变量这一点也可以看出。那THREAD_POOL_EXECUTOR线程池调度器则是根据CPU数构造线程Thread缓存池,节约资源,提高AsyncTask执行性能。
也许你不理解,为什么AsyncTask默认把它设计为串行执行的呢?
由于一个进程内所有的AsyncTask都是使用的同一个线程池执行任务;如果同时有几个AsyncTask一起并行执行的话,恰好AysncTask的使用者在doInbackgroud里面访问了相同的资源,但是自己没有处理同步问题;那么就有可能导致灾难性的后果!
由于开发者通常不会意识到需要对他们创建的所有的AsyncTask对象里面的doInbackgroud做同步处理,因此,API的设计者为了避免这种无意中访问并发资源的问题,干脆把这个API设置为默认所有串行执行的了。如果你明确知道自己需要并行处理任务,那么你需要使用executeOnExecutor(Executor exec,Params… params)这个函数来指定你用来执行任务的线程池,同时为自己的行为负责。(处理同步问题)
讲完exe.execute(mFuture)
,其中的参数mFuture
我们还没讲,下面看看它:
|
|
mFuture
是FutureTask的一个实例,它的初始化也是在AsyncTask的构造函数中,代码如下:
|
|
简单来说,mFuture包装了这个mWorker对象,在这个mFuture的run函数(文中没有给出这部分代码)中又会调用mWork对象的call方法,在call方法中又调用了doInBackground()
方法。因为mFuture提交给了线程池来执行,所以,使得doInBackground()
执行在非UI线程。得到doInBackground()
的结果后,通过postResult()
传递结果给UI线程。这一段逻辑我们已经在开始讲过了,这样就可以和前面连接起来了。
而且我们可以看到,mFuture实例对象的done()
方法中,如果捕捉到了CancellationException类型的异常,则发送一条“MESSAGE_POST_CANCEL”的消息;如果顺利执行,则发送一条“MESSAGE_POST_RESULT”的消息,而消息都与一个sHandler对象关联。
设计模式
- 模版方法模式
AsyncTask中用到的一个很明显的设计模式就是模版方法模式,一个execute()
方法中封装了onPreExecute()
、doInBacground()
、onPostExecute()
这几个流程,用户可以根据自己的需求再复写这几个方法。这个模式理解起来很简单,就不再做太多介绍。
疑惑
- 既然AsyncTask的任务是串行执行的,也就是一个任务一个任务的执行,那使用线程池的意义又是什么呢?希望对这个有理解的同行可以在我的简书下留言,确实是不解。
参考: