天天看点

Asynctask 不一样的解读

Asynctask

先看看官宣也就是AsyncTask的类注释,这部分非常重要:

AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask可以适当且方便地使用UI线程。此类允许您执行后台操作并在UI线程上发布结果,而无需操作线程和handler。

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) …

AsyncTask旨在成为{@link Thread}和{@link Handler}的助手类,并不构成通用的线程框架。理想情况下,AsyncTasks应该用于短操作(最多几秒钟)。

When first introduced, AsyncTasks were executed serially on a single background thread. Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are executed on a single thread to avoid common application errors caused by parallel execution.

首次引入时,AsyncTasks在单个后台线程上串行执行。 从Android 1.6开始,这被改为一个线程池,允许多个任务并行运行。 从Android 3.0开始,任务在单个线程上执行,以避免由并行执行引起的常见应用程序错误。

简要的解读一下注释:

  • Asynctask可以让你在异步的同时简单的更新UI线程,而不是使用Thread和Handler这种复杂的组合。
  • 不构成通用的线程框架。asynctask应该用于短时间的CPU密集型操作。
  • 首次引入时,AsyncTasks在单个后台线程上串行执行。 从Android 1.6开始,这被改为一个线程池,允许多个任务并行运行。 从Android 3.0开始,任务在单个线程上执行,以避免由并行执行引起的常见应用程序错误。

实现原理

Asynctask集合了FutureTask、Handler、LinkedBlockingQueue、ArrayDeque…优点。有时间可逐一学习此处安且不表。

FutureTask的优点:

下面这两点是Thread无法直接实现的。

  • 线程结束可以将结果返回
  • 线程可取消

Handler优点:

  • 实现了Android线程间的通信

LinkedBlockingQueue优点

  • 线程安全的双端队列,遵循FIFO原则,因为队列的特性他的增删效率很高(数组和链表的区别)。

Asynctask的优缺点

优点

  • Asynctask重写doInBackGround方法就能实现异步任务,可以说是最简单的任务并发方式,并且在onProgressUpdate/onPostExecute中能直接更新UI。
  • Asynctask作为内部类的时候能直接访问外部类的UI控件,这一点比用Thread和handler要少写无数行代码。

缺点

  • 线程池等待队列大小已经设定好了128,当创建快于销毁容易超出这个范围。网络谣传,从源码上看不出这个值会起到作用。
  • Asynctask直接持有activity或fragment,执行超长时间的任务容易内存泄漏,简短的泄漏可忽略。
  • 一个项目中所有的AysncTask都是串行执行,对的你没看错,当你想要高并发却发现自己在串行的单行道上时,简直有一万头神兽奔过。

综上所述:官方不建议用asynctask来替代Thread来执行超长时间的任务,如果需要的话还是用Thread或者FeatureTask。

源码解读

程序猿之间没有什么是用代码沟通不了的,如果有那就加上注释。

几个重要属性

public final WorkerRunnable<Params, Result> mWorker
  • 这个属性实现了

    java.util.concurrent.Callable

    ,这是AsyncTask能被取消的原因之一。
public final FutureTask mFuture
  • FutureTask<V> implements RunnableFuture<V>

    虽然他继承了Runnable但是他只做状态管理,详细见源码。
public static final Executor THREAD_POOL_EXECUTOR;
  • 这是一个线程执行调度器,这个线程池申明为常量类型,说明一个项目中所有的AsyncTask的子类公用一个线程池。
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
  • 这又是一个线程执行调度器,不仅static还volatile说明了这个调度器的原子性。再看看他的名字SERIAL_EXECUTOR,串行执行器。
public static final BlockingQueue sPoolWorkQueue
  • 这是一个线程安全的双端队列,遵循FIFO原则,因为队列的特性他的增删效率很高(数组和链表的区别)。
final ArrayDeque mTasks = new ArrayDeque();
  • 这是

    SerialExecutor

    中的一个对象,他是一个并非线程安全的双端队列,遵循FIFO原则(继承自Deque双端队列都FIFO)。

下面我们来看看两个线程调度器和两个任务队列之间的故事。

常见的属性就不聊了,直接看重点。

sPoolWorkQueue

// 线程保活时间
private static final int KEEP_ALIVE_SECONDS = 30;
// 创建任务管理队列
private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
// 创建线程池
 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
// 判断是否要移出队列中的任务
Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
           

解读一下:

我觉得网上说,快速建立AsyncTask并执行会导致崩溃的原因是只看到了

workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)

这半句,当存活时间超过30秒则移出队列。其实只要超过sPoolWorkQueue队列上限就会阻塞在加入队列之外,同时每次从sPoolWorkQueue获取任务时,判断队列长度达到63就开始移出队首的任务。

SerialExecutor

/**
     * 一个{@link Executor},它按顺序执行一个任务。 此序列化对于特定过程是全局的。
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

	private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    // AsyncTask.excute最终执行的是这个方法,
    //这个方法整体加了同步锁,也就是说execute()方法是串行执行的
    public synchronized void execute(final Runnable r) {
        // 创建一个新Runnable对象,该对象才是外部线程池中真正执行的任务
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    //新Runnable对象运行的时候,FutureTask当作普通对象运行
                    r.run();
                } finally {
         //FutureTask当作普通对象运行,所以只有当FutureTask的run方法执行完,
         //才加入让下一个任务进入到外部的调度器执行
                    scheduleNext();
                }
            }
        });
        /* 第一次的加入任务后,mActive始终不为空也就是说,
        只有在上面新任务运行时,上一个FutureTask任务执行结束了,
        才会把下一个任务加入到THREAD_POOL_EXECUTOR调度器去管理
         这样就保证了
         当最后一个任务执行完,这个时候mActive为空,再次尝试查找一下
        */ 
        if (mActive == null) {
            scheduleNext();
        }
    }
	//SerialExecutor移出最前面的任务,让THREAD_POOL_EXECUTOR调度器去管理
    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

           

解读一下:

  1. FutureTask当作普通对象用。
  2. 不管你同时创建多少个Task并执行,最终都是串行执行的。
  3. 既然是串行的,那sPoolWorkQueue的大小设置为128,如果同时加入的任务过多会导致崩溃应该是谣言。

有图为证:

Asynctask 不一样的解读

消息机制和方法调用

// 所有的事件都在工作线程通知到asyncTask的handler
    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @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
        // 更新状态时通知UI线程,onProgressUpdate()方法中能直接更新UI原因在此
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
        // 更新状态时通知UI线程,onProgressUpdate()方法中能直接更新UI原因在此
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
    // 将InternalHandler初始化为主线程的handler
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

           

之所以

onPostExecute()

onProgressUpdate()

方法中能直接更新UI原因是因为本身已经封装好了Handler。

应用

// 同时执行多个任务
new MyTask().execute("任务1","任务二","任务三".......);

  class MyTask extends AsyncTask<String,String,String>{

        @Override
        protected String doInBackground(String... strings) {
            // 执行完成任务,可以用publishProgress触发onPostExecute            
            publishProgress("asdf");
            // 任务进度更新,可以用publishProgress触发onProgressUpdate  
            publishProgress("");
            return null;
        }


        @Override
        protected void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
        }
    }
           

错误用法

场景:把多张图放到多个线程执行压缩操作压缩完之后通知主线程,使用AsyncTask偷个懒,代码如下方式:

for(int i=0;i<9;i++){
    new MyTask().execute("任务"+i);
}
           

然后我们看看结果:

虽然是在多线程上运行,但是确实串行的按顺序执行。这样不仅没有性能优化,反而因为创建线程带来了额外的开销。所以说AsyncTask只能是Thread和Handle的辅助工具,并不能进行替换。

Asynctask 不一样的解读

总结

官方不建议用asynctask来替代Thread来执行超长时间的任务,如果需要的话还是用Thread或者FeatureTask。

有多处与网上的主流观点相悖,如有错误欢迎打脸。

继续阅读