美文网首页
AsyncTask解析思考

AsyncTask解析思考

作者: 贝贝ovo | 来源:发表于2018-08-28 13:27 被阅读47次

    一.什么是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类是在主线程加载。因此AsyncTaskHandler用的也是主线程的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是并行执行的吗?
    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, "");
    

    思考:为什么默认不并行?

    因为 AsyncTask 内部的线程池是静态、单例的,所以在同一进程中所有用到 AsyncTask的地方都是同一个线程池,如果我们创建了多个 AsyncTask且在其中访问了共同资源,并且没做同步处理,那么并行时就会出现同步问题。Google 可能觉得这很麻烦,为了避免各种问题,便在 3.0 以后改成了默认串行执行。

    四.AsyncTask的缺点及注意点

    • 必须在主线程中加载,不然在API 16以下不可用,但目前来说,大部分app最低版本也到16了,这个缺点可以忽略了
    • 内存泄露
      Activity中使用非静态匿名内部AsyncTask类,会持有外部类的隐式引用。由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。改为静态内部类
    • Activity 已被销毁,doInBackground还没有执行完,执行完后再执行 onPostResult, 导致产生异常 ,记得在ActivityonDestroy 方法中调 cancel方法取消 AsyncTask
    • AsyncTask不适合特别耗时的任务,原因就是上述两点,1.内存泄漏 2.生命周期没有跟Activity同步,建议用线程池
    • 每个AsyncTask实例只能执行一次。

    参考
    再谈Android AsyncTask的优缺点
    AsyncTask的使用方式和版本演进

    相关文章

      网友评论

          本文标题:AsyncTask解析思考

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