
- AsyncTask是什么?能解决什么问题 ⭐⭐⭐⭐
- 给我谈谈AsyncTask的三个泛型参数作用以及它的一些方法作用。⭐⭐⭐
- 给我说说AsyncTask的原理。⭐⭐⭐
- 你觉得AsyncTask有不足之处吗?有何使用注意事项?⭐⭐⭐
AsyncTask介绍
更新UI的方式有哪些?
- Activity.runOnUiThread(Runnable)
- View.post(Runnable),View.postDelay(Runnable, long)
- Handler
- AsyncTask
- Rxjava
- LiveData
AsyncTask是一个轻量级的异步任务类,可以在线程池里执行比较耗时的后台任务,然后把执行的进度和最后的结果传递给主进程,并在主进程中更新UI。使用容易,而且好理解。
AsyncTask的使用
同样从使用入手,看看代码。以下代码以往文件里写数据为例:
1 | private class FileTestTask extends AsyncTask<Void, Long, Boolean> { //1 |
1.2 核心方法
- onPreExecute():字面上就是“提前执行”,会在后台任务开始前调用,并在主线程中执行,因此常用于初始化一些数据和界面。
- doInBackground(Params…):字面上是“在背后处理”,就是在后台处理所有耗时的任务,这些任务都会在子线程中处理。有两个需要注意的点:
- 不能在这个函数进行UI操作,因为该函数是在子线程运行的。如果需要更新当前任务完成进度条,需要运行publishProgress(Progress…),如上面的注释2,而最终是调用onProgressUpdate()去更新进度条;
- 执行完之后可以通过return将执行的结果返回,返回的结果会在onPostExecute()里执行,如注释4,我这边只是更新了一个文字内容。
- onProgressUpdate(Progress…):如上一段所述,调用publishProgress(Progress…)后会把参数(如当前任务处理的进度)传递给onProgressUpdate()并被调用,因为该方法在主进程进行,因此可以进行UI修改。
- onPostExecute():如上所述,doInBackground()会把执行结果返回给 onPostExecute()并被调用,该方法也在主线程运行,可以做一些结尾的动作,如提示任务完成等。
上面几个核心方法的调用顺序为: onPreExecute() –> doInBackground() –> publishProgress() –> onProgressUpdate() –> onPostExecute()。
如果不需要更新进度则为onPreExecute() –> doInBackground() –> onPostExecute()。
三个泛型类参数
在1.1小节案例代码里的注释1 可以看到AsyncTask<Void, Long, Boolean>,其原型是 AsyncTask<Params, Progress, Result>,这三个泛型类参数含义如下:
- Params:开始异步任务执行时传入的参数类型,对应doInBackground(),本案例是Void,代表不需要传入参数;
- Progress:异步任务执行过程中,返回下载进度值的类型,对应onProgressUpdate(),本案例是Long,代表目前已经写入的字节数;
- Result:异步任务执行完成后,返回的结果类型,对应onPostExecute(),本案例返回的是Boolean类型,true代表任务完成;
AsyncTask使用心得
AsyncTask的使用注意事项
- AsyncTask的实例需要在UI线程也就是主线程中创建,调用execute()开始执行任务时,也要在主线程执行;
- 不要手动调用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法;
- 只有onPreExecute(),onProgressUpdate(Progress… values),onPostExecute(Result result)可以执行UI相关操作,doInBackground(Params… params)不能更改UI界面;
- 一个任务实例只能执行一次execute(),执行多次会报异常。
AsyncTask的不足之处
- 生命周期:AsyncTask不与任何组件绑定生命周期,因此会出现组件生命周期结束了,但AsyncTask还存在,所以需要在onDestroy()调用 cancel(boolean),手动结束AsyncTask;
- 内存泄漏:和之前说的Handler造成内存泄露的原因是一样的。如果AsyncTask被声明为Activity的非静态的内部类,如本文使用代码案例一样,那么AsyncTask会保留一个所在Activity的引用。如果Activiy被销毁,此时因此AsyncTask保留了它的引用,导致Activity无法被回收,引起内存泄露。
- 结果丢失:如果屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity重新创建,如果之前运行的AsyncTask是非静态的内部类,此时就会持有之前旧的Activity的引用,而旧的Activity已经销毁了,这时调用onPostExecute()再去更新界面将不再生效,无法更新到新的Activity到界面。
源码分析
回顾下AsyncTak的使用方法:我们只要执行new FileTestTask().execute(),就会自动执行onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。这是怎么做到的呢?
带着这个思路,按顺序,我们先看看在初始化一个AsyncTask时,调用函数都做了什么。
1 | public AsyncTask() { |
我们可以看到这仅仅是初始化了两个变量,mWorker和mFuture,并在初始化mFuture的时候将mWorker作为参数传入,如注释1和注释4。此时仅仅完成初始化,什么都没做。不过记住这个注释1的call()函数,里面有注释2和注释3两个关键函数,后面是如何调用到的呢?接着执行execute()就开始执行任务了,来看看execute()的源码:
1 | public final AsyncTask<Params, Progress, Result> execute(Params... params) { |
注释5,execute()调用executeOnExecutor(sDefaultExecutor, params),终于在注释6,看到了熟悉的函数onPreExecute()!执行完onPreExecute(),即完成了一些初始化工作后,调用注释7:exec.execute(mFuture)开始执行耗时任务,并把上面注释4初始化的mFuture实例传入。
进一步看看注释7这个exec是什么?看下代码调用,就知道exec是注释5的sDefaultExecutor,因此注释7的exec.execute(mFuture)实际上是执行sDefaultExecutor.execute(mFuture)。再追述源码可以看到sDefaultExecutor是SerialExecutor类,具体源码如下:
1 | public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); |
可以看出这个SerialExecutor是静态内部类,因此每个AsyncTask都共有。SerialExecutor维护了一个队列,也就是注释8的mTasks。在注释10通过加锁保证AsyncTask的任务是串行的。好好看注释10这个execute()函数,主要做了两件事:
- 在注释11向队列mTask加入一个新的任务,也就是注释7传入的mFuture对象;
- 调用注释 13的scheduleNext()方法,进而调用注释14的THREAD_POOL_EXECUTOR.execute(mActive)来执行队列头部任务。
所以,SerialExecutor只是保证任务是串行的,真正执行任务还是交给THREAD_POOL_EXECUTOR。继续追代码,看看THREAD_POOL_EXECUTOR是什么?
1 | ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( |
注释16可以看到THREAD_POOL_EXECUTOR就是一个线程池,注释15调用了线程池的execute()方法,执行具体的耗时任务,而注释15的mActive是从注释14的mTasks队列获取的Runnable,这个Runnable又是注释11加入队列的,注释11这个Runnable就是注释7传入的mFuture,接着这个mFuture又是注释4传入的mWorker构造的。通过倒推,就知道注释15执行的具体的耗时任务其实就是注释1中的call()函数。终于,调用到了注释1,先执行完doInBackground()方法处理后台耗时任务,又执行postResult()方法返回处理结果,一切都和我们想的一样!
最后看看怎么结束任务吧。
1 | private Result postResult(Result result) { |
没错,就是Handler机制。AsyncTask内部又一个Handler对象,这也是为什么2.1小节说AsyncTask需要在主线程创建,因为Handler需要在主线程创建:
1 | private static class InternalHandler extends Handler { |
代码非常清晰,如果收到MESSAGE_POST_PROGRESS消息,在1.1小节注释2的地方发送,就执行注释19,又是我们熟悉的onProgressUpdate(),如果收到MESSAGE_POST_RESULT消息,就执行注释18的finish()。
1 | private void finish(Result result) { |
如果按2.2小节说的调用cancel(boolean),则会回调注释20,否则执行注释21:onPostExecute(result)。
总结
AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler),其中线程池SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务,InternalHandler用于将执行环境从线程池切换到主线程。