Android 异步技术-AsyncTask
简介
AsyncTask
是Android系统提供的一个轻量级的异步工具类,基于Executor
框架封装, 官方对其的定义如下:
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 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
是被设计用来更容易的使用UI线程,允许在后台线程完成耗时的操作,然后把结果发布到UI线程中,它并不是一个通用的异步框架,所以一般情况下,只应用于一些最多几秒钟简短的操作,如果后台线程需要运行更长的时间,则应该考虑直接使用线程池。
优势
- 方便的实现了UI线程和后台线程的异步通信,不需要自己通过使用Thread+Handler这种较为复杂的组合实现
- 基于线程池实现,在应用范围内所有的
AsyncTask
复用了统一的线程池资源,避免了频繁的线程创建/销毁带来的开销
不足
- 使用不当易导致内存泄漏
如果AsyncTask
被声明为Activity非静态的内部类,由于Java的非静态内部类默认会持有一个外部类的引用,此时如果activity被关闭,而AsyncTask
仍然在执行后台操作的话,会导致activity未被回收,导致内存泄漏。
- 结果丢失
当activity因为配置变更(例如旋转屏幕)导致重建时,之前运行的AsyncTask
会持有重建之前的activity的引用,此时的结果回调是无法修改UI的。
- 生命周期
AsyncTask
不会随activity的销毁自行销毁,在关闭activity时需正确的取消任务。
- 在不同版本中任务的 并行/串行 执行顺序的表现不一致,官方对其任务执行顺序表述如下:
When first introduced, AsyncTasks were executed serially on a single background thread. Starting with Build.VERSION_CODES.DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with Build.VERSION_CODES.HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.
If you truly want parallel execution, you can invoke executeOnExecutor(Executor, Object[]) with THREAD_POOL_EXECUTOR.
从官方说明可以看出,在API版本低于11(Android 3.0)的时候,AsyncTask
的任务执行顺序在不同的版本是不一样的,有些版本是串行执行,有些版本是并发执行,所以如果应用仍需兼容低版本的系统,在使用AsyncTask
的时候就需要仔细考虑任务的执行顺序,必要时需要针对不同的系统做兼容处理,不过目前应该很少有应用还需要兼容这么老的系统了,所以基本可以忽略此差异。
基础使用
AsyncTask
是一个抽象类,声明如下:
public abstract class AsyncTask<Params, Progress, Result> {
...
}
// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
// a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
// b. Progress:异步任务执行过程中,返回下载进度值的类型,不需要进度可使用java.lang.Void
// c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
}
在使用时首先需要实现自己的业务子类,例如:
public static class MyTask extends AsyncTask<String,Integer,String>{
@Override
protected void onPreExecute() {
// 运行线程:主线程
// 是否必须:否
// 方法说明:任务执行前调用,可在此做一些后台任务执行前的准备工作,例如UI提示等
}
@Override
protected String doInBackground(String... strings) {
// 运行线程:后台线程
// 是否必须:是
// 方法说明:执行后台耗时任务,接收输入的不定长参数列表,返回任务执行结果
return null;
}
@Override
protected void onPostExecute(String s) {
// 运行线程:主线程
// 是否必须:否
// 方法说明:接收后台任务执行结果,用于将后台执行结果刷新到界面中
}
@Override
protected void onProgressUpdate(Integer... values) {
// 运行线程:主线程
// 是否必须:否
// 方法说明:后台任务执行进度通知,在执行后台任务时调用 publishProgress(Progress... values) 方法触发。
}
@Override
protected void onCancelled(String s) {
// 运行线程:主线程
// 是否必须:否
// 方法说明:当cancel(boolean)方法被调用,并且doInBackground(String... strings)执行完毕时被调用,接收的参数为后台线程执行结果
// 如果复写此方法,则不应调用super.onCancelled(s)
}
@Override
protected void onCancelled() {
// 运行线程:主线程
// 是否必须:否
// 方法说明:当cancel(boolean)方法被调用,并且doInBackground(String... strings)执行完毕时被调用,忽略后台线程执行结果
}
}
业务子类实现完之后直接提交任务执行即可:
MyTask myTask = new MyTask();
myTask.execute("params1","params2","params3");
需要注意的是,通过execute
方法提交的任务,在3.0版本之后,一个应用内所有的AsyncTask
提交的任务默认都是串行执行的,如果需要任务并发执行,则需要调用executeOnExecutor
方法,传入线程池,线程池可以使用AsyncTask
提供的AsyncTask.THREAD_POOL_EXECUTOR
,例如:
myTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"params1","params2","params3");
实现原理
本节基于Android 10源码分析 AsyncTask
的具体实现原理,在简介中有提到,AsyncTask
是基于Executor
框架封装封装的一个轻量级的异步工具类,也就是说在它的内部是通过线程池来实现的线程调度和复用,另外为了将工作线程的执行结果通知到主线程,还使用了Android的Handler机制处理工作线程和主线程之间的通信问题。
线程池
通过上面 的介绍,我们已经知道了AsyncTask
是使用线程池来执行具体的任务的,那我们首先来看看它内部线程池相关的部分:
private static final int CORE_POOL_SIZE = 1;
private static final int MAXIMUM_POOL_SIZE = 20;
private static final int KEEP_ALIVE_SECONDS = 3;
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
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;
}
THREAD_POOL_EXECUTOR
就是AsyncTask
内部用来执行任务的线程池,可以看到它被声明为静态的,也就是说在同一个进程内部,所有的AsyncTask
都是使用的同一个线程池,具体的相关配置参数为:核心线程数=1,最大线程数=20,非核心线程最长存活时间=3S,采用直接提交队列。即该线程池只有一个核心线程,当有多个任务同时提交时,会最多开启20个行程去并发执行任务,当有更多的任务被提交时,会执行自定义的拒绝策略sRunOnSerialPolicy
:
private static final int BACKUP_POOL_SIZE = 5;
// Used only for rejected executions.
// Initialization protected by sRunOnSerialPolicy lock.
private static ThreadPoolExecutor sBackupExecutor;
private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue;
private static final RejectedExecutionHandler sRunOnSerialPolicy =
new RejectedExecutionHandler() {
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size");
// As a last ditch fallback, run it on an executor with an unbounded queue.
// Create this executor lazily, hopefully almost never.
synchronized (this) {
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);
}
};
在自定义的拒绝策略里,为了节约系统资源,采用延迟初始化的方式加载了一个线程池sBackupExecutor
,并用其内部的对象锁保证只初始化一次,这个线程池被定义为核心线程数和最大线程数都是5,并采用无界队列,同时允许核心线程超时销毁。
任务调度-串行
在内部线程池的代码中并没有看到任何的同步代码,那么AsyncTask
是通过什么方式保证了任务的串行执行呢?直接看在使用中调用的串行执行方法execute
的代码:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
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.mParams = params;
// 执行任务
exec.execute(mFuture);
return this;
}
可以看到,execute
内部还是调用的是executeOnExecutor
方法,可是这个方法在上面使用部分中介绍的明明是用于执行并发逻辑的,此处的串行又是怎么实现的呢?其实关键就在于传入的线程池参数sDefaultExecutor
,正是由它保证了所有任务的串行执行,下面看看它的实现
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
// 注意,此处为静态实例,也就是说同一个进程内,所有的AsyncTask都使用同一个Executor,并且使用了volatile修饰,保证了多线程环境中的可见性
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private static class SerialExecutor implements Executor {
// 双向队列,用于存储所有的任务
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
// 使用synchronized锁保证多任务执行时是串行执行
public synchronized void execute(final Runnable r) {
// 将任务加入队列中
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
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
通过源码分析可以对AsyncTask
串行执行流程总结如下:
定义了一个进程内全局的串行任务调度器sDefaultExecutor
,在这个调度器内使用队列缓存了所有通过execute
方法提交的任务,并且使用synchronized
关键字对保证在多线程环境下任务的提交与执行是串行进行的,但是sDefaultExecutor
并不提供具体的执行任务的能力,任务是是由默认的线程池THREAD_POOL_EXECUTOR
执行的。
任务调度-并发
在理解了串行执行的逻辑之后,并发的处理逻辑就很简单了,在基础使用一节中有提到如果需要并发执行,只需调用executeOnExecutor
方法,传入线程池,线程池可以使用AsyncTask
提供的AsyncTask.THREAD_POOL_EXECUTOR
,由于传入的线程池并没有使用任何的同步方法,所以提交的任务自然就根据线程池的配置并发的执行了。
任务执行与线程通信
下面是AsyncTask
的初始化方法:
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
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);
}
}
};
}
可以看到首先初始化了一个Handler
,用于线程间的通信,然后初始化了一个WorkerRunnable
对象,这个对象实现了Callable
接口,同时存储了任务提交时的参数信息,最后WorkerRunnable
对象又作为参数传给了FutureTask
,用于等待任务执行完成,通知结果,而这个FutureTask
对象正是在任务调度中,最终被提交给线程池运行的task,所以整个的任务执行的逻辑可以总结如下:
- 业务层通过调用
execute/executeOnExecutor
方法传入任务执行参数 - 使用
WorkerRunnable
对象包装传入的业务层参数, - 提交
FutureTask
到线程池中执行 -
WorkerRunnable
对象的 call 方法被调用,回调到业务层实现的doInBackground
方法 - 当
doInBackground
方法执行完毕时,FutureTask
对象的done方法被回调,通过handler机制回调结果到主线程