• AsnycTask内部实现原理


     AsnycTask 原理就是“线程池 + Handler”的组合。

    使用线程池的主要原因是避免不必要的创建及销毁线程的开销。

    AsyncTask 里的线程池:

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE = 1;
    
    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
    
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
    
    private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
    
    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    从上面我们可以看到,

    1、该线程池(即 THREAD_POOL_EXECUTOR)的核心线程数为 CPU 的核心数量 + 1,

    2、最大线程数为 CPU 的核心数量 * 2 + 1,

    3、过剩线程的存活时间为1s。这里要注意的是 sPoolWorkQueue 是静态阻塞式的队列,意味着所有的 AsyncTask 用的都是同一个 sPoolWorkQueue ,也就是说最大的容量为128个任务,若超过了会抛出异常。同时执行的线程数是5个线程。最后一个参数就是线程工厂了,用来制造线程。

    4、AysnTask的线程池一般是串行的。也有并行的。

    我们再来看看 AsyncTask 内部的任务执行器 SERIAL_EXECUTOR ,该执行器用来把任务传递给上面的 THREAD_POOL_EXECUTOR 线程池。在 AsyncTask 的设计中,SERIAL_EXECUTOR 是默认的任务执行器,并且是串行的,也就导致了在 AsyncTask 中任务都是串行地执行。当然,AsyncTask 也是支持任务并行执行的,这个点我们在下面再讲。

    执行结果调用postResult方法,源码:

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

    在以上源码中,先调用了getHandler方法获取AsyncTask对象内部包含的sHandler,然后通过它发送了一个MESSAGE_POST_RESULT消息。我们来看看sHandler的相关代码:

    private static final InternalHandler sHandler = new InternalHandler();
    
     private static class InternalHandler extends Handler {
          public InternalHandler() {
              super(Looper.getMainLooper());
          }
    
          @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
          @Override
         public void handleMessage(Message msg) {
             AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
             switch (msg.what) {
                 case MESSAGE_POST_RESULT:
                     // There is only one result
                     result.mTask.finish(result.mData[0]);
                     break;
                 case MESSAGE_POST_PROGRESS:
                     result.mTask.onProgressUpdate(result.mData);
                     break;
             }
         }
     } 

    从以上代码中我们可以看到,sHandler是一个静态的Handler对象。我们知道创建Handler对象时需要当前线程的Looper,所以我们为了以后能够通过sHandler将执行环境从后台线程切换到主线程(即在主线程中执行handleMessage方法),我们必须使用主线程的Looper,因此必须在主线程中创建sHandler。这也就解释了为什么必须在主线程中加载AsyncTask类,是为了完成sHandler这个静态成员的初始化工作。

    在以上代码第10行开始的handleMessage方法中,我们可以看到,当sHandler收到MESSAGE_POST_RESULT方法后,会调用finish方法,finish方法的源码如下:

    private void finish(Result result) {
         if (isCancelled()) {
             onCancelled(result);
        } else {
             onPostExecute(result);
         }
         mStatus = Status.FINISHED;
     }

    第2行,会通过调用isCancelled方法判断AsyncTask任务是否被取消,若取消了则调用onCancelled方法,否则调用onPostExecute方法;在第7行,把mStatus设为FINISHED,表示当前AsyncTask对象已经执行完毕。

    经过了以上的分析,我们大概了解了AsyncTask的内部运行逻辑,知道了它默认使用串行方式执行任务。那么如何让它以并行的方式执行任务呢? 阅读了以上的代码后,我们不难得到结论,只需调用executeOnExecutor方法,并传入THREAD_POOL_EXECUTOR作为其线程池即可。

    AsyncTask主要存在以下局限性:

    • 在Android 4.1版本之前,AsyncTask类必须在主线程中加载,这意味着对AsyncTask类的第一次访问必须发生在主线程中;
      在Android 4.1以及以上版本则不存在这一限制,因为ActivityThread(代表了主线程)的main方法中会自动加载AsyncTask
    • AsyncTask对象必须在主线程中创建
    • AsyncTask对象的execute方法必须在主线程中调用
    • 一个AsyncTask对象只能调用一次execute方法

      原因:

      AsyncTask实例必须UI线程创建的原因如下:
      需要在主线程创建InternalHandler,以便onProgressUpdate, onPostExecute , onCancelled 可以正常更新UI。
      AsyncTask实例的execute方法必须在主线程调用的原因如下:
      保证 onPreExecute 正常的更新UI。
      ---------------------

    AsyncTask的缺陷和问题

    1、生命周期

           很多开发者会认为一个在Activity中创建的AsyncTask会随着Activity的销毁而销毁。然而事实并非如此。AsyncTask会一直执行, 直到doInBackground()方法执行完毕。然后,如果 cancel(boolean)被调用, 那么onCancelled(Result result) 方法会被执行;否则,执行onPostExecute(Result result) 方法。如果我们的Activity销毁之前,没有取消 AsyncTask,这有可能让我们的AsyncTask崩溃(crash)。因为它想要处理的view已经不存在了。所以,我们总是必须确保在销毁活动之前取消任务。总之,我们使用AsyncTask需要确保AsyncTask正确地取消。

           另外,即使我们正确地调用了cancle() 也未必能真正地取消任务。因为如果在doInBackgroud里有一个不可中断的操作,比如BitmapFactory.decodeStream(),那么这个操作会继续下去。

    2、内存泄漏

            如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。

    3、结果丢失

           屏幕旋转或Activity在后台被系统杀掉等情况会导致Activity的重新创建,之前运行的AsyncTask会持有一个之前Activity的引用,这个引用已经无效,这时调用onPostExecute()再去更新界面将不再生效。

    4、并行还是串行

          在Android 1.6之前的版本,AsyncTask是串行的,在1.6至2.3的版本,改成了并行的。在2.3之后的版本又做了修改,可以支持并行和串行,当想要串行执行时,直接执行execute()方法,如果需要并行执行,则要执行executeOnExecutor(Executor)。

    缺陷:AsyncTask中维护着一个长度为128的线程池,同时可以执行5个工作线程,还有一个缓冲队列,当线程池中已有128个线程,缓冲队列已满时,如果

        此时向线程提交任务,将会抛出RejectedExecutionException。

    解决方法:由一个控制线程来处理AsyncTask的调用判断线程池是否满了,如果满了则线程睡眠否则请求AsyncTask继续处理。

  • 相关阅读:
    Docker _简单使用
    IDEA常见问题
    Linux安装JDK
    vitualbox网络设置链接
    MQ对比
    乐观锁和悲观所在数据库中的实现
    11.08 JS知识
    11.07知识整理
    11.06 知识整理
    本周知识整理
  • 原文地址:https://www.cnblogs.com/Jackie-zhang/p/9717316.html
Copyright © 2020-2023  润新知