一.什么是AsyncTask?
AsyncTask
封装了线程池和Handler
,是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程,并在主线程中更新UI。简单讲就是方便开发者在子线程中更新UI(因为内部集成了Handler
,所以它可以很灵活的在UI线程和子线程之间进行切换)
二.AsyncTask应该有哪些方法
要做到如上述定义那般,可猜测它应该提供以下几点内容:
1.能定义以下执行过程的操作:
预执行、执行后台任务、执行进度反馈、执行完毕2.能随时创建,执行,停止线程任务
3.子线程能与UI线程交互(反馈线程执行进度和执行结果)
第1点,定义这些方法目的是让调用者重写而实现各种交互,因此这四种操作实际上是四个抽象方法(或者空方法)AsyncTask
类中已经在合适的时机调用了这四个抽象方法,显然,这是模板方法模式(Template Method
)的应用。
查看AsyncTask源码,找到上面所提到的对应的四个方法
protected void onPreExecute() {} //预执行
protected abstract Result doInBackground(Params... params); //执行后台任务
protected void onProgressUpdate(Progress... values) {}//执行进度反馈
protected void onPostExecute(Result result) {} //执行完毕
可以看到,AsyncTask
是一个抽象的泛型类,需要通过继承的方式来使用。且有三个方法是有参数的,正好对应AsyncTask
需要指定得三个三个泛型参数的类型
public abstract class AsyncTask<Params, Progress, Result>
- Params:传递给后台任务的参数类型。
- Progress:后台任务执行进度的类型。
-
Result:后台任务执行完成返回的结果类型。
如果AsyncTask
确实不需要传递具体的参数,那么这三个泛型参数可以用Void
来代替
第2点
能随时创建,执行,停止线程任务
对应的方法
mTask = new DownloadTask(); //创建实例
mTask.execute("Hello"); //执行
mTask.cancel(true); //停止
- 核心方法
execute()
源码流程太多文章分析了,这里偷懒就简单用找来的一张图概括了
AsyncTask.png - 取消方法
cancel()
AsyncTask 提供了 cancel 方法来取消,需传一个 boolean 类型的参数
true 表示会 intercept 中断 doInBackground,
false 不会中断 doInBackground, 它会完整地执行完。
true/false 都不会再执行 onPostResult 方法。
第三点
子线程能与UI线程交互,那想必是用Handler
了 源码中对应InternalHandler
想到好多文章都说
AsyncTask 的对象必须在主线程中创建。
但真的是这样么,我自己跑了一个在子线程创建,执行任务,是可以的啊,并且也可以更新UI啊,蛤,怀疑人生。各种查资料,好吧了解了AsyncTask
跟我一样命途多舛
三.AsyncTask版本演进
1、必须在主线程初始化?
(1)在Android 4.1(API 16)之前,AsyncTask在成员位置直接声明并创建了Handler对象,它是一个静态变量,也就是说它是在类加载的时候创建的。源码如下:
private static final InternalHandler sHandler = new InternalHandler();
于是,InternalHandler
用了当前线程的Looper
,此时需要保证AsyncTask
在主线程初始化,从而保证InternalHandler
对应的Looper
是主线程。
(2)从Android 4.1(API 16)开始,到Android 5.1(API 22)之前,在ActivityThread的main函数里面(App启动的入口),直接调用了AsyncTask的init()方法,该方法代码如下:
public static void init() {
sHandler.getLooper();
}
main
函数运行在主线程,这样就确保了AsyncTask
类是在主线程加载。因此AsyncTask
的Handler
用的也是主线程的Looper
。但是,不管我们需不需要AsyncTask
,它都会在我们App启动时就加载,如果我们完全没有用到它,很浪费资源。
(3)从Android 5.1(API 22)开始,在AsyncTask成员变量位置仅仅声明了静态的Handler,并没有创建对象,如下:
private static InternalHandler sHandler; // 还去掉了final关键字
在Handler的实现中,添加了一个无参构造:
public InternalHandler() {
super(Looper.getMainLooper());
}
并添加了getHandler()
方法,在需要Handler
时会调用此方法。
(注意 InternalHandler
的构造函数,这家伙把主线程的looper
传给了handler
,无论在哪个线程创建AsyncTask
,最终的回调还是会在主线程)
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
这样既保证了 Handler
采用主线程的 Looper
构建,又使得 AsyncTask
在需要时才被加载。
因此,结论是在Android 4.1(API 16)之前,必须在主线程初始化AsyncTask,创建实例,从Android 4.1(API 16)开始,就不用了。
到此明白了大家为什么说AsyncTask
的对象必须在主线程中创建,这句话并不绝对。
并且还了解了AsyncTask
串行,并行的发展史,不要想当然的以为AsyncTask
用到了线程池,就肯定是并行的
2、AsyncTask是并行执行的吗?
- 在Android 1.6(API 4)之前,AsyncTask是串行执行任务。
2.在Android 1.6(API 4)到 Android 3.0(API 11)之前,AsyncTask是并行执行任务。
3.从Android 3.0(API 11),AsyncTask是串行执行任务。如果此时想执行并行任务,可以使用executeOnExecutor方法,如
new MyAsyncTask("AsyncTask#1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "");
思考:为什么默认不并行?
因为 AsyncTask
内部的线程池是静态、单例的,所以在同一进程中所有用到 AsyncTask
的地方都是同一个线程池,如果我们创建了多个 AsyncTask
且在其中访问了共同资源,并且没做同步处理,那么并行时就会出现同步问题。Google
可能觉得这很麻烦,为了避免各种问题,便在 3.0 以后改成了默认串行执行。
四.AsyncTask的缺点及注意点
- 必须在主线程中加载,不然在API 16以下不可用,但目前来说,大部分app最低版本也到16了,这个缺点可以忽略了
- 内存泄露
在Activity
中使用非静态匿名内部AsyncTask
类,会持有外部类的隐式引用。由于AsyncTask
的生命周期可能比Activity的长,当Activity进行销毁AsyncTask
还在执行时,由于AsyncTask
持有Activity的引用,导致Activity
对象无法回收,进而产生内存泄露。改为静态内部类 -
Activity
已被销毁,doInBackground
还没有执行完,执行完后再执行onPostResult
, 导致产生异常 ,记得在Activity
的onDestroy
方法中调cancel
方法取消AsyncTask
- AsyncTask不适合特别耗时的任务,原因就是上述两点,1.内存泄漏 2.生命周期没有跟
Activity
同步,建议用线程池 - 每个
AsyncTask
实例只能执行一次。
网友评论