AsyncTask的使用方式和版本演进

AsyncTask是一个轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AsyncTask可以方便地执行后台任务以及在主线程中访问UI,但是AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时的任务来说,建议使用线程池。

AsyncTask的使用方式

AsyncTask这个类的声明如下:

public abstract class AsyncTask 

这是一个抽象的泛型类,其中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 {
    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艺术探索》

(2)[Android] AsyncTask 解析

(3)为什么Android的AsyncTask不适合执行长时间操作的任务

(4)译文:Android中糟糕的AsyncTask

你可能感兴趣的:(AsyncTask的使用方式和版本演进)