美文网首页
AsyncTask的使用方式和版本演进

AsyncTask的使用方式和版本演进

作者: Aisen | 来源:发表于2018-08-27 00:04 被阅读100次

    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艺术探索》

    (2)[Android] AsyncTask 解析

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

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

    相关文章

      网友评论

          本文标题:AsyncTask的使用方式和版本演进

          本文链接:https://www.haomeiwen.com/subject/atwaiftx.html