Android的AsyncTask

什么是AsyncTask?

AsyncTask是一个高度封装化的异步工作类. 它的实现原理仍然是Handler的机制, 但是因为封装的程度很高, 所以我们使用起来也很方便. 虽然它的实现原理还是我们也会用的Handler的机制, 但是AsyncTask的开销是比我们平常写的Handler+Message小的. 原因在于, AsyncTask内部实现了两个线程池(注意, 这个面试的时候也会问到), 避免了不断地创建和销毁线程.

AsyncTask的原理

AsyncTask的源码逻辑虽然不是很难, 但是它很长, 所以这里就只用大白话介绍一些它的实现原理, 大家可以自己去看源码来验证.

首先, AsyncTask的主要结构是两个线程池——SerialExecutor和THREAD_POOL_EXECUTOR. 前者是一个串行的线程池, 用来对要执行的后台任务按顺序排队;后者是真正的执行任务的线程池. 初次之外, AsyncTask当然也要有一个Handler——InternalHandler. 它负责将工作环境从线程池切换到主线程, 以便进行UI的更新.

如果你看了源码, 你就会发现, THREAD_POOL_EXECUTOR这个线程池是静态的. 也就是说, 它管理着整个进程的所以AsyncTask子类实例. 这就有可能产生一个问题: 如果你同时使用了过多的子类实例, 线程池会爆掉. 当然, 解决办法也是有的, 你可以自己写一个线程池来取代它.

AsyncTask的工作流程

其实工作流程我们在写代码的时候不怎么会碰到(至少我没有碰到过). 不过鉴于AsyncTask是面试时的一个热点, 所以我还是简单地学习了一下它的工作流程.

AsyncTask的工作流程如下:

  1. 启动: 将参数Params封装为FutureTask对象. Params我们下面会提到, FutureTask就是一个Runnable.

  2. 排队: 调用SerialExcutor的execute方法, 将FutureTask放入任务队列中.

  3. 执行: 当线程池有空闲的线程时, 任务队列中的FutureTask就会被拿出并执行.

AsyncTask的使用

AsyncTask子类的声明

首先我们先看一下AsyncTask的声明:

1
2
3
4
5
6
public abstract class AsyncTask<Params, Progress, Result>{
// Params: 开始异步任务执行时传入的参数类型
// Progress: 异步任务执行过程中, 返回下载进度值的类型
// Result: 异步任务执行完成后, 返回的结果类型
// 如果AsyncTask确定不需要传递具体参数, 那么这三个泛型参数可以用Void来代替.
}

我们看到, AsyncTask有三个泛型. 从左到右依次是开始异步任务时要传入的参数类型、异步任务执行进度值的参数类型和异步任务执行完成后返回的结果类型. 这三个泛型在什么时候会用到我们下面会提到, 这里我们只要知道它们就是三个泛型, 如果不需要参数就写Void就好.

doInBackground方法

这个方法是必须重写的一个方法. 它的形式如下:

1
protected abstract Result doInBackground(Params... params);

这个方法就是用来执行异步任务的, 因此它在后台执行.

onPreExecute方法

这个方法是在执行doInBackground方法前执行的方法, 它的形式如下:

1
protected void onPreExecute();

这个方法是在主线程进行的, 所以理论上是可以用来初始化界面或者获取界面数据什么的. 不过现在都流行MVVM+数据仓库的应用架构, AsyncTask的子类一般都放在数据仓库里作为静态内部类, 所以(我)不怎么使用这个方法.

onProgressUpdate方法

这个方法是在执行doInBackground方法过程中执行的方法, 它的形式如下:

1
protected void onProgressUpdate(Progress... values);

这个方法是在主线程进行的, 我们也很经常用. 一般来说, 更新进度条都是用这个方法实现的.

onPostExecute方法

这个方法是在执行完doInBackground方法后执行的方法, 它的形式如下:

1
protected void onPostExecute(Result result);

这个方法也是在主线程进行的, 一般异步解析完数据后, 会在这个方法内将数据传递给ViewModel或者Adapter什么的, 也可以在这个方法内做一些提示任务已完成的Toast.

onCancelled方法

这个方法是在执行doInBackground方法过程中执行的方法, 它的形式如下:

1
protected void onCancelled(Result result);

顾名思义, 这个方法是用来取消任务的. 说是取消, 其实只是任务不再进行而已, 已经进行的部分还需要人为地去还原. 显然, 这个方法也是在主线程进行的. 需要补充的是, 如果调用了这个方法, 那么onPostExecute方法就不会被调用了. 这是当然的, 因为任务被取消了, 也就不会完成了.

AsyncTask的注意事项

  1. AsyncTask必须要在主线程创建, 因为SerialExcutor的execute方法需要在主线程执行.

  2. 一个任务实例只能执行一次, 如果执行第二次将会抛出异常. 就是说, 每执行一次异步任务, 都要新创建一个实例. 这也是可以理解的, 因为任务从队列拿出并执行后就被销毁了.

  3. 就像前面提到的, AsyncTask的子类一般都作为静态内部类. 理由和Handler需要被声明为静态内部类一样: 如果是非静态的内部类, 因为它会持有创建它的Activity的引用, 这将导致Activity不会被回收, 进而出现内存泄漏.

  4. AsyncTask的生命周期不和任何Activity或者Fragment绑定. 即使创建它的Activity或者Fragment被销毁了, 它也会继续存在. 这一点是AsyncTask会出现内存泄漏的原因. 同时, 我们也应该有这样一个意识: 一旦我们要销毁这个Activity或者Fragment, 一定要记得取消执行的内部任务. 这并不困难, 我们知道在onDestroy方法里调用cancel方法就好了.