DMingO

本文很多资料基于Google Developer官方对AsyncTask的最新介绍。

AsyncTask 是什么

image-20200727110522173
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

上文翻译:AsyncTask 是一个被设计为围绕Thread和Handler操作的工具帮助类,而不是作为通用的线程框架,理想情况下,应将AsyncTasks用于短操作(最多几秒钟)。如果需要长时间保持线程运行,Google建议使用java.util.concurrent这个并发包提供的各种API,例如 ExecutorThreadPoolExecutorFutureTask

!This class was deprecated in API level 30.
Use the standard java.util.concurrent or Kotlin concurrency utilities instead.

目前官方已经明确说明,AsyncTask 将会在API 30,也就是Android 11的版本中,将这个类废弃掉。使用java.util.concurrent和Kotlin的协程组件代替AsyncTask 。

谷歌要将AsyncTask废弃掉的原因,我猜测是:AsyncTask 是一个很古老的类了,在API Level 3的时候就有了,还有着许多致命的缺点,终于连Google都忍不了,加上目前已经有许多替代的工具了,如Kotlin协程等。

AsyncTask的缺陷

However, the most common use case was for integrating into UI, and that would cause Context leaks, missed callbacks, or crashes on configuration changes. It also has inconsistent behavior on different versions of the platform, swallows exceptions from doInBackground, and does not provide much utility over using Executors directly.

译:

  • AsyncTask 最常见是子类继承然后直接用在 UI层的代码里,这样容易导致Context的内存泄漏问题
  • Callback回调的失效
  • 配置改变时的崩溃
  • 不同版本行为不一致:最初的AsyncTasks在单个后台线程上串行执行;Android1.6时它更改为线程池,允许多个任务并行运行;Android 3.2时又改为只会有单个线程在执行请求,以避免并行执行引起的常见应用程序错误。
  • 在它的重要方法doInBackground中会将出现的异常直接吞掉
  • 多个实例调用execute后,不能保证异步任务的执行修改顺序合理
  • 在直接使用Executors方面没有提供太多实用性

缺点真的蛮多的,简单用用可能还行,但是要考虑清楚

AsyncTask的参数和重要方法

定义子类时需设置传入的三大参数类型<Params , Progress , Result>,如果某类型未用到的将它设为Void

  1. Params(在执行AsyncTask时需要传入的参数,可用于在后台任务中使用)

  2. Progress(后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位)

  3. Result(当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型)

定义子类时可重写四大方法:onPreExecute,onProgressUpdate,doInBackground,onPostExecute

  • onPreExecute()

    这个方法会在后台任务开始执行之间调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等。

  • doInBackground(Params...)

    子线程中运行此方法,可以将几秒的耗时任务在这里运行。任务一旦完成就可以通过return语句来将任务的执行结果进行返回。若Result类型,指定了Void,就不会返回任务执行结果。如果想要更新当前的任务进度想在UI中显示出来,可以通过 publishProgress(Progress...)。

  • onProgressUpdate(Progress...)

    doInBackground(params)调用了publishProgress(Progress...)方法后,此方法中会被调用,传递的参数值就是在后台任务中传递过来的,类型是上面说到的Progress的类型。这个方法是在UI线程操作,对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。

  • onPostExecute(Result)

    当后台任务执行完毕并通过return语句进行返回时,返回的数据会作为参数传递到此方法中,可以利用返回的Result来进行一些UI操作。,

AsyncTask开始简单的异步任务

简单来说:AsyncTask是一个抽象类,必须被子类化才能使用,这个子类一般都会覆盖这两个方法doInBackground(Params...)onPostExecute(Result),创建AsyncTask子类的实例执行execute方法就运行异步任务了。

//最简单的AsyncTask实现方式
public class DownloadTask extends AsyncTask<String, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }
    @Override
    protected Boolean doInBackground(String... strings) {
        return null;
    }
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
    }
}

//在UI线程中启用 AsyncTask
new DownloadTask().execute();

使用AsyncTask要遵守的规矩

  • 必须在UI线程上加载AsyncTask类。

  • 必须在UI线程上创建 AsyncTask子类的实例。

  • 必须在UI线程上调用 execute(Params ...)。

  • 不要手动调用onPreExecute,onPostExecute,doInBackground,onProgressUpdate这几个方法

  • 该任务只能执行一次(如果尝试第二次执行,则将引发异常。)

好鸡肋的设定啊,不知道当初为什么要这样设计

AsyncTask源码分析

先由一行最简单的启动AsyncTask的代码入手:

new DownloadTask().execute("");

进入execute方法查看:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

先看下sDefaultExecutor这个属性是什么名堂:

	//sDefaultExecutor 被 volatile修饰
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

	//串行执行任务线程池实例
	public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

    private static class SerialExecutor implements Executor {
        //维护一个Runnable的队列
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;
		
        //往队尾中压入一个Runnable的同时运行队头的Runnable,维护队列的大小
        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }
		//弹出,执行队头的Runnable
        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
	
	//创建核心线程数为 1,最大线程容量为20的线程池
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(), sThreadFactory);
        threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

可以看到的是sDefaultExecutor,是一个只可以串行执行单个任务的线程池,被调用execute时会将新的Runnable压入任务队列,如果Runnable mActive == null的话会取出队头的Runnable执行,而每当一个任务结束后都会执行任务队列中队头Runnable。

这样做的目的是:保证在不同情况,只能有一个任务可以被执行,SerialExecutor做出了单一线程池的效果。每当一个任务执行完毕后,下一个任务才会得到执行,假如有这么一种情况,主线程快速地启动了很多AsyncTask任务,同一时刻只会有一个线程正在执行,其余的均处于等待状态。

再来看看executeOnExecutor这个方法又有什么名堂:

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        //若任务状态不是尚未执行
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                //任务状态为正在运行,抛出异常    
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
				//任务状态为已完成,抛出异常
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }
		//将任务状态更改为正在运行
        mStatus = Status.RUNNING;
		//执行由子类负责实现细节的抽象方法,预执行
        onPreExecute();
        mWorker.mParams = params;
        //由上文提到的 sDefaultExecutor 执行 FutureTask<Result>任务
        exec.execute(mFuture);

        return this;
    }
	//Callable的子类,泛型类型为<Params, Result>
    private final WorkerRunnable<Params, Result> mWorker;
	
    private final FutureTask<Result> mFuture;

executeOnExecutor 执行方法,会先检查是否运行了这个任务或者已结束,由于AsyncTask任务规定每个任务只能执行一次,不符合就会抛出异常。接着开始调用 onPreExecute 开始预执行,然后给mWorker赋值参数,执行

mFuture蕴含的任务。到这里好像就没了?非也非也,还有很关键的AsyncTask的构造函数

相关文章: