AsyncTask是Android从1.5引入的类,它内部做了比较完整的封装。借助它,我们可以很easy的在子线程进行耗时操作,在主线程处理结果,而不用操作Thread或者Handler。即使到目前,用AsyncTask做类似文件的上传或下载也是很方便的。
使用就不多介绍了,就是按照AsyncTask的泛型定义,自己构造子类继承AsyncTask。
初始化
继承AsyncTask的子类在初始化的时候,会调用AsyncTask的无参构造函数,构造函数必须在UI线程调用,具体原因后说。查看源码,精简后如下:
mHandler=getMainHandler();
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
......
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
......
}
};
这段代码,初始化了一个Handler对象mHandler,一个Callable对象mWorker和一个FutureTask对象mFuture,并且将mWorker作为mFuture的参数传入。但这些只是定义好了回调和一些异常处理,还没有真正的触发执行。
任务的执行
开启任务,需要执行execute()
,具体源码:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params){
return executeOnExecutor(sDefaultExecutor, params);
}
注释写的很清楚了,必须在主线程调用。接着看:
@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;
exec.execute(mFuture);
return this;
}
这里就比较清楚了,首先对mStatus做了状态判断,证实任务处于未执行状态时,将任务的状态置为执行中,同时调用onPreExecute
,这里子类可以做一些初始化工作。因为execute
在主线程执行,因此该方法也是在UI线程中被调用。最后用传进来的Executor对象去执行mFuture中定义的任务,因为FutureTask也是实现了Runnable接口的。这里的Executor对象就是sDefaultExecutor,这是个什么东西?
sDefaultExecutor
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
在AsyncTask初始化的时候就为我们提供了一个默认的Executor对象,并且是静态类型的,也就是说该进程内所有的AsyncTask子类都共用这一个对象。再来看SerialExecutor的源码:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
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.execute(mActive);
}
}
}
该类内部维护了一个双端队列mTasks,当开始执行时新建一个Runnable放入mTasks队尾,这里面调用mFuture的run
方法,点到FutureTask的源码里面可以看到,它引用了初始化时传入的Callable对象,也就是mWorker,并调用其call
方法,这就和前面构造函数初始化的时候串起来了。
前面说了,一个进程中不管new多少AsyncTask实例都是公用的一个SerialExecutor。实际上新建一个Runnable加入到队尾,并没有触发任务开始,什么时候开始执行呢?就是THREAD_POOL_EXECUTOR.execute(mActive)
。在第一个任务调用execute
方法时,mActive为null,执行scheduleNext
方法。该方法从队列头部取值如果不为空,则赋值给mActive并开始执行。如果该任务耗时比较长,那么下一个任务进来的时候mActive不为null,不会立即执行scheduleNext
。这时候真正执行的时机是,第一个任务在自己执行完毕后一定要走finally代码块中的scheduleNext
,这时第二个任务已经被加入到队尾中,自然就可以保证执行了。这说明SerialExecutor实际上是一个单线程类型的Executor,在任务耗时稍长而有多个任务的情况下,同一时刻只有一个任务执行,执行完毕后才能执行下一个任务。
THREAD_POOL_EXECUTOR
SerialExecutor这个线程池保证了任务的线性执行,真正执行任务的线程池是THREAD_POOL_EXECUTOR。该线程池在AsyncTask类被加载时创建:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
AsnycTask默认构造的这个线程池,核心线程和最大线程数和CPU期望核心数有关,keepAliveTime为30s,阻塞队列长度为128。最大线程数虽然不大,但是因为任务实际是单线程执行的,并不会抛异常。
任务的执行流程
回过头来,再看异步任务mWorker和mFuture的初始化过程:
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 (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
前面说过FutureTask的run()
会调用传入的Callable对象的call()
,也就是mWorker的call()
,一旦call
开始执行,mTaskInvoked置为true,这是一个原子型变量,标记任务开始执行。try代码块中又调用了doInBackground(mParams)
,这样就完成了异步调用。过程中如果发生异常,原子变量mCancelled会置为true。最终获取doInBackground
的返回结果,并传递给postResult
。该方法源码:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
显然这个就是Handler的异步消息处理机制。其中的AsyncTaskResult是AsyncTask内部封装的一个消息实体。而发送消息的Handler就是getHandler()
返回的mHandler,它是在mWorker初始化之前通过getMainHandler()
创建了一个静态内部类InternalHandler对象,并且和主线程绑定:
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
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
这里就可以看到,handleMessage
里就切回到了主线程,并开始处理接收到的异步信息。因为前面发送的是MESSAGE_POST_RESULT,而result.mTask就是AsyncTask对象本身,所以是调用了类内部的finish()
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
到这里,如果mCancelled不为true,也就是在上述任务执行过程中未发生异常或主动取消,那么就会调用onPostExecute(result)
,所以我们可以重写这个方法,在UI线程处理返回的结果。当然如果取消了任务,也可以重写onCancelled(result)
做一些异常处理。最后将mStatus置为FINISHED。
更新任务进度
注意到消息类型还有一个MESSAGE_POST_PROGRESS,发现在publishProgress
方法里发送了这个消息:
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
这个方法AsyncTask本身自己不会调用,而是由使用者在子类的doInBackground
中调用。InternalHandler收到消息后开始处理,又会调用onProgressUpdate
。这样使用者可以获取任务执行的进度,并且在主线程实时更新,最常用的就是下载文件显示进度条。
使用缺陷
从上面的分析看,AsyncTask为我们封装好了一整套Thread、Handler、Executor、Runnable,包括异常处理,泛型和回调,有很好的思想。当然,它也有一些缺陷,无法适用一些场景。
首先就是最初AsyncTask是单线程依次执行,从安卓1.6开始,变为多线程并行执行,但同时最多支持128个任务并发,一旦任务耗时稍长而又有很多个任务的情况下就很容易抛异常。因此从安卓3.0以后,又引入了SerialExecutor,等于还是默认单线程一个一个任务执行。其实安卓也意识到了这个问题,所以它的executeOnExecutor(Executor exec,Params... params)
允许我们可以传入自己定义的Executor,而不是默认的SerialExecutor。比如下例子:
private void execute() {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 150; i++) {
new MyTask().executeOnExecutor(executorService);
}
}
private class MyTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
try {
Log.i(TAG, Thread.currentThread().getName());
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
可以看到快速打印出来的日志,而不是每隔1s打印一次日志,确实是并行的。什么场景要用到并行执行?很典型的一个例子就是本地相册,因为Bitmap的解析不能放到UI线程中进行,而如果使用AsyncTask的串行执行,能想像向下滑动时图片要一个一个的依次加载出来?但是实际上还是有很多坑。
即使是采用并行执行任务,如果相册里有几千张图片甚至更多,初次加载没有缓存的话,当快速滑动的时候仍然会发生卡顿。就是当任务负载量短时间内比较高的情况下,doInBackground没有及时得到执行的原因,在一帧(16ms)内没有完成任务,造成了丢帧。具体为什么,我认为可能是因为是使用FutureTask包装了任务执行的原因,降低了程序整体效率。因为FutureTask本身就适用于很耗时的任务,而AsyncTask注释又说明了自身适合的场景是短时间的轻量任务,也不知道谷歌为何要这么设计,虽然已经满足了大部分需求了。除此之外,AsyncTask还存在如在组件中的生命周期、内存泄漏等缺陷。因此高负载量的后台任务,还是用ThreadPoolExecutor+Runnable比较好。