基于Android10.0代码深入了解终将老去的AsyncTask

3,321

一、前言

相信AsyncTask对每一个开发者来说都非常熟悉,它是一个轻量级的异步任务类。同时,它也经历了很多个版本的调整,如实现上从开始串行执行,再到并行执行,再后来又改回串行执行。AsyncTask从Android API3已经出现,它之所以能够屹立不倒这么多年,想必有很多值得我们学习的地方,本文将基于Android Q(10.0)的源码对AsyncTask进行分析。

二、关于Deprecated

当我准备开始阅读AsyncTask源码的时候,我在AsyncTask的官方文档发现了它在Android R(11.0)上已经被标记过时,官方更推荐开发者使用Kotlin的协程进行异步操作。

image

Google官方列举了以下把它标记为过时的原因,其实这也是AsyncTask一直以来都被诟病的地方:

  1. 容易导致内存泄漏
  2. 忘记回调
  3. 横竖屏切换导致崩溃
  4. 不同版本的AsyncTask的兼容问题

三、使用

也许你很久已经没有使用AsyncTask,所以我觉得有必要先帮大家重温一下AsyncTask的使用方法,如果你已经非常熟悉它的使用,可以跳过本部分。

AsyncTask使用也是非常简单,只需要进行两步即可完成异步任务的执行,这里就一笔带过。

继承AsyncTask

class MyAsyncTask : AsyncTask<Void, Void, Void>() {
    /**
    *  任务执行前的操作,可以不重写,执行在UI线程
    **/
    override fun onPreExecute() {
        super.onPreExecute()
    }
    
    /**
    * 必须重写的方法,需要在异步线程执行的任务都在这里面执行,执行在子线程
    **/
    override fun doInBackground(vararg params: Void?): Void? {
        Log.i("MyAsyncTask", "doInBackground")
        return null
    }
    
    /**
    * doInBackground执行完会把result返回给这个方法,一般是做UI的更新,执行在UI线程
    **/
    override fun onPostExecute(result: Void?) {
        super.onPostExecute(result)
    }
}

执行AsyncTask

// 在需要的时候执行AsyncTask
val mTask = MyAsyncTask()
mTask.execute()

四、串行还是并行?

相信熟读面经的你肯定知道AsyncTas的执行顺序,当然太过久远的版本源码我也没有看,我觉得知道一个大概就行了,具体如下:

  • Android1.6以前,是串行执行,实现原理是一个用一个子线程进行任务的串行执行。
  • Android1.6到2.3,是并行执行,实现原理是用一个线程数为5的线程池进行并行执行,但是如果前5个任务执行时间过长,就会阻塞后面任务的执行,所以不适合大量任务并发执行。
  • Android3.0之后,是串行执行,使用一个全局的线程池进行串行处理任务。

当然,你可能记不住哪些版本怎么执行,我觉得这个不重要,你只需要记住目前Android版本使用的就是串行执行就可以了,因为Android Q(10.0)的源码就确实是这么做的。

1.如何验证串行执行?

举个例子,同时执行三个AsyncTask,每个AsyncTask的doInBackground方法里面延时2s钟输出一个Log,按道理来说,如果并行执行,大概2s左右(最多误差几十毫秒)就能把三个Log都打印出来;反之,如果是串行执行,整个执行流程就需要大概6s左右。下面,我们来验证下,代码也是非常简单。

class MyAsyncTask(index: Int) : AsyncTask<Void, Void, Void>() {
    private var mIndex = index
    override fun onPreExecute() {
        super.onPreExecute()

        Log.i("MyAsyncTask", "AsyncTask$mIndex onPreExecute.")
    }

    override fun doInBackground(vararg params: Void?): Void? {
        Thread.sleep(2000)
        Log.i("MyAsyncTask", "AsyncTask$mIndex doInBackground. thread=${Thread.currentThread().name}")
        return null
    }

    override fun onPostExecute(result: Void?) {
        super.onPostExecute(result)
        Log.i("MyAsyncTask", "AsyncTask$mIndex onPostExecute.")
    }
}

class MainActivity : AppCompatActivity() {
    private var mTask: MyAsyncTask? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 循环三次执行
        for (i in 1..3) {
            mTask = MyAsyncTask(i)
            mTask?.execute()
        }
    }
}

输出结果:

2020-03-18 11:51:15.286 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask1 onPreExecute.
2020-03-18 11:51:15.287 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask2 onPreExecute.
2020-03-18 11:51:15.287 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask3 onPreExecute.
2020-03-18 11:51:17.288 18164-18259/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask1 doInBackground. thread=AsyncTask #1
2020-03-18 11:51:17.290 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask1 onPostExecute.
2020-03-18 11:51:19.295 18164-18351/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask2 doInBackground. thread=AsyncTask #2
2020-03-18 11:51:19.296 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask2 onPostExecute.
2020-03-18 11:51:21.299 18164-18352/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask3 doInBackground. thread=AsyncTask #3
2020-03-18 11:51:21.301 18164-18164/com.ryan.async_task_demo I/MyAsyncTask: AsyncTask3 onPostExecute.

通过结果可以知道,整个过程耗时6s左右,而且,每个Task的doInBackground都是顺序执行的,由此可以证明,在Android Q上,AsyncTask默认是串行执行异步任务的。

2.可以改成并行执行吗?

Android官方知道使用默认实现肯定满足不了各种各样的使用场景,所以它暴露了executeOnExecutor接口,让开发者自己实现执行异步任务的线程池,所以我们可以指定AsyncTask的线程池,从而使它变成并行执行。

  public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)

线程池我们可以手动实现一个,当然AsyncTask里面已经实现了一个核心线程数为1,最大线程数为20,3s线程存活的线程池:

private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;

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;
}

我们可以直接使用这个线程池执行AsyncTask,即可实现并行执行:

for (i in 1..3) {
    mTask = MyAsyncTask(i)
    mTask?.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
}

五、实现原理

在讲解实现原理之前,我先给大家抛出几个问题,为什么使用了线程池就可以让AsyncTask默认是串行执行?为什么手动设置了一个线程池,AsyncTask就变成了异步执行?它的内部实现到底是怎么样的?如果你还是不太了解,带着这些疑问阅读会比较好理解。

1.串行执行原理/执行过程原理

在AsyncTask里面定义了一个静态内部类SerialExecutor,SerialExecutor实现了Executor接口,对外暴露一个execute接口(注意这个接口是用synchronized修饰的),同时有一个它的实例静态变量sDefaultExecutor,也就是说sDefaultExecutor在一个进程里面是只有一个对象,即每一个AsyncTask都是共用这个sDefaultExecutor。

那么当多个AsyncTask调用execute时,就会触发执行sDefaultExecutor的execute方法,由于这个方法是同步的,那么调用的就会持有SerialExecutor的锁,其他AsyncTask再调用execute的时候,就需要等到前一个AsyncTask释放锁之后才能调用,这样就实现了多个AsyncTask的串行执行。具体流程图如下所示。

image

结合源码一起来看它的实现:

(1).执行任务的线程池

在AsyncTask内部创建了一个线程池,核心线程数是1,最大线程数是20,这个线程池最终是进行异步任务执行的,以下代码就是它的一个初始化,并没有执行逻辑:

// 核心线程数是1
private static final int CORE_POOL_SIZE = 1;
// 最大线程数20
private static final int MAXIMUM_POOL_SIZE = 20;
// 空闲线程存活时间3s
private static final int KEEP_ALIVE_SECONDS = 3;

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;
}

(2).保持串行执行的Executor

在AsyncTask里面还创建了一个保证串行执行的Executor,即上面流程图所说的静态实例,所有的AsyncTask都是共用同一个Executor,它还用volatile来修饰,保证了多线程对它的可见性,

// 串行执行的Executor
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
// 静态实例
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// SerialExecutor实现了Executor接口
private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    // execute方法用synchronized保证了多任务执行时是串行执行
    public synchronized void execute(final Runnable r) {
        // 竞争到锁后,把它放到ArrayDeque里面
        mTasks.offer(new Runnable() {
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        if (mActive == null) {
            // 触发执行
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
        if ((mActive = mTasks.poll()) != null) {
            // 执行的时候可以看到,
            // 最终还是回到了线程池THREAD_POOL_EXECUTOR进行任务处理
            // SERIAL_EXECUTOR只是作为一个保证串行执行的执行器
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

(3).触发执行SerialExecutor的execute方法

当我们在业务层调用了asyncTask.execute()的时候,就调用了AsyncTask的execute方法,然后把excute的参数赋值给mWorker(它是一个Callable),mFuture再把mWorker进行一次包装,至于用FutureTask而不是用一个Runnable的原因,就是FutureTask能够获取结果,而Runnable并不能。最终触发执行到SerialExecutor的execute,然后再把FutureTask塞到线程池THREAD_POOL_EXECUTOR中执行。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    // 由于是没有指定AsyncTask的执行器
    // 所以是使用了上面说的sDefaultExecutor,触发串行执行
    return executeOnExecutor(sDefaultExecutor, params);
}

// 最终在executeOnExecutor里面处理执行的逻辑
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
    onPreExecute();

    // 把参数复制给mWorker,mWorker是一个Callable
    mWorker.mParams = params;
    // mFuture是一个FutrueTask,它是执行mWorker的载体
    // 最终触发2中SerialExecutor的execute方法
    exec.execute(mFuture);

    return this;
}

2.实现并行执行的原理

在《串行还是并行》那一部分我们知道,只要修改AsyncTask内部的执行器,就可以实现把串行执行改为并行执行:

mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)

查看源码可知,executeOnExecutor实际上是把默认的sDefaultExecutor替换成了THREAD_POOL_EXECUTOR,而sDefaultExecutor就是通过synchronized实现了任务的串行执行,而THREAD_POOL_EXECUTOR并没有把execute方法改成同步,所以自然就变成了异步执行。

 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
    ...

    onPreExecute();

    mWorker.mParams = params;
    // 使用了传入的Executor执行,而不是sDefaultExecutor
    exec.execute(mFuture);

    return this;
}

3.onProgressUpdate和onPostExecute实现原理

我们都知道onProgressUpdate是进行进度更新,onPostExecute是在子线程任务执行完后回调到UI线程更新UI的方法。所以这两个方法都是在UI线程执行的,那么子线程和UI线程是如何进行通信的呢?

相信聪明的你一定猜到了,那就是Handler,Handler是Android里面实现线程通信的工具,AsyncTask子线程就是通过获取UI线程的Handler,然后把消息发送到UI线程的消息列表,从而实现onProgressUpdate和onPostExecute的回调。

(1)初始化Handler

初始化AsyncTask的时候,会生成UI线程的Handler。

public AsyncTask(@Nullable Looper callbackLooper) {
    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
                ? getMainHandler()
                : new Handler(callbackLooper);
}

(2)更新进度

当使用时主动调用publishProgress时,就会通过UI线程的Handler,把消息推到UI线程的消息队列。

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

private static class InternalHandler extends Handler {
    public InternalHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

(3)执行结果

在执行原理中,我有提到,一个任务就是一个FutrueTask,它的实现如下,当任务执行完了之后,就会回调到done方法,然后调用postResultIfNotInvoked(get())方法,这个方法就是把消息通过Handler分发回UI线程,然后收到消息后触发finish方法,在finish里面进行回调到onPostExecute。

mFuture = new FutureTask<Result>(mWorker) {
    @Override
    protected void done() {
        try {
            postResultIfNotInvoked(get());
        } catch (InterruptedException e) {
            android.util.Log.w(LOG_TAG, e);
        } catch (ExecutionException e) {
            throw new RuntimeException("An error occurred while executing doInBackground()",
                    e.getCause());
        } catch (CancellationException e) {
            postResultIfNotInvoked(null);
        }
    }
};

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

/**
* 最终也是通过Handler发送事件
**/
private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

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:
                // 调用AsyncTask的finish方法
                result.mTask.finish(result.mData[0]);
                break;
       
        }
    }
}

private void finish(Result result) {
    if (isCancelled()) {
        onCancelled(result);
    } else {
        // 最后回调到onPostExecute
        onPostExecute(result);
    }
    mStatus = Status.FINISHED;
}

4.异常处理

我们已经知道,AsyncTask执行的线程池是THREAD_POOL_EXECUTOR,它在创建时,设置了拒绝执行的策略sRunOnSerialPolicy,即当THREAD_POOL_EXECUTOR拒绝执行任务的时候,具体的处理逻辑就会分发到sRunOnSerialPolicy里面的rejectedExecution方法,这时候AsyncTask就会获取一个备用的线程池(如果不存在则创建出来),这个线程池核心线程数是5个,然后让备用线程池执行这个被拒绝的任务。

private static final int BACKUP_POOL_SIZE = 5;

private static ThreadPoolExecutor sBackupExecutor;
private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue;

private static final RejectedExecutionHandler sRunOnSerialPolicy = new RejectedExecutionHandler() {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        // 如果任务被拒绝执行,就会回调到这个方法里面
        synchronized (this) {
            // 这里会触发一个备用的线程池sBackupExecutor进行任务处理
            if (sBackupExecutor == null) {
                sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
                sBackupExecutor = new ThreadPoolExecutor(
                        BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
                        TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
                sBackupExecutor.allowCoreThreadTimeOut(true);
            }
        }
        sBackupExecutor.execute(r);
    }
};

六、总结

通过翻阅Android Q中AsyncTask的源码,我们知道了AsyncTask默认是串行执行任务,它里面使用了两个“线程”池,其中一个是伪线程池,它只是实现了Executor接口,并把execute方法用synchronized,从而实现多任务时串行执行,而另一个线程池则是真实执行任务的线程池。

此外,AsyncTask在设计上也是支持我们把任务执行顺序改为并行的,只要通过修改它的执行任务的线程池即可。

在线程切换方面,AsyncTask通过Handler来实现子线程和UI线程的通信,从而实现进度变化和UI更新。

再者,AsyncTask设置了一个备用的线程池,以便常规执行任务的线程池拒绝执行任务时,还能保证任务队列里面的任务正常执行。

最后,AsyncTask在Android R(11.0)中已经被标记Deprecated,在如今异步操作框架百花齐放的时代,它多多少少显得有点落伍,但是我们也不要忘记它曾经的辉煌,一代人终将老去,但总有人正年轻,它已经很好地完成了它的使命感。