AsyncTask是一个轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以方便地执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。
AsyncTask的使用方式
AsyncTask这个类的声明如下:
public abstract class AsyncTask<Params, Progress, Result>
这是一个抽象的泛型类,其中Params
表示参数的类型,Progress
表示后台任务的执行进度的类型,Result
表示后台任务的返回结果的类型,不需要传递参数时,用Void代替。
AsyncTask提供了4个核心方法:
(1)onPreExecute()
,在主线程中执行,在异步任务执行之前,此方法会被调用,一般可以用于做做一些准备工作。
(2)doInBackground(Params... params)
,在线程池中执行,此方法用于执行异步任务,params参数表示异步任务的输入参数。在此方法中可以通过publishProgress方法来更新任务的进度,publishProgress方法会调用onProgreeUpdate方法。另外,此方法需要返回计算结果给onPostExecute方法。
(3)onProgreeUpdate(Progress... values)
,在主线程中执行,当后台任务的执行进度发生改变时,此方法会被调用。
(4)onPostExcute(Result result)
,在主线程中执行,在异步任务执行之后,此方法会被调用,其中result参数是后台任务的返回值,即doInBackground的返回值。
上面几个方法,onPreExecute
先执行,接着是doInBackground
,最后是onPostExcute
。示例如下:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
//下载的总字节数
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled())
break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
执行的方法如下:
try {
new DownloadFilesTask().execute(new URL("http://www.baidu.com"),
new URL("http://www.renyugang.cn"));
} catch (MalformedURLException e) {
e.printStackTrace();
}
AsyncTask版本演进
1、串行 or 并行
(1)在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, "");
2、必须在主线程初始化?
(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时会调用此方法。
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方便地执行后台任务以及在主线程中访问UI,但是对于特别耗时的后台任务,建议使用线程池,这是为什么呢?
有以下原因:
(1)AsyncTask的生命周期没有跟Activity的生命周期同步。
如果你在一个Activity中创建了一个AsyncTask,你旋转了屏幕,这个Activity将会被销毁并且会重新创建一个新的实例。
但是AsyncTask没有销毁,它将会继续执行直到完成。当它执行完成后,它实际上是更新了上一个已经不存在的Activity,如果你原本想在onPostExecute()中更新UI的话,这时的AsyncTask将不会更新新的Activity,并且这个操作会引发一个异常:java.lang.IllegalArgumentException。
如果你是想要在onPostExecute()中关闭一个dialog,则会发生:java.lang.IllegalArgumentException: View not attached to window manager(前提是你忘记在Activity的onStop()中忘记dismiss)。不仅如此,还会在show()的调用出抛出一个lead window的异常:
Activity com.xxx.XXXActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{5350b3f4 V.E..... R....... 0,0-729,192} that was originally added here
(2)内存泄漏
在Activity中作为内部类创建AsyncTask很方便。因为AsyncTask在执行完成或者执行中需要使用Activity中的view,因为内部类可以直接访问外部类的域(也就是变量)。然而这意味着内部类会持有外部类的一个引用。当长时间运行时,因为AsyncTask持有Activity的引用,所以即使当该Activity不再显示时Android也无法释放其占用的资源。
参考
(1)任玉刚《Android艺术探索》