相信有很多同学都使用过AsyncTask,都知道onPreExecute,onProgressUpdate,onPostExecute方法均运行在主线程,doInBackground方法运行在子线程,可以在其中做一些耗时操作,在doInBackground方法中也可以调用publishProgress方法来更新UI(之后会调用到onProgressUpdate方法)。不知道大家有没有好奇过,为什么onPreExecute,onProgressUpdate,onPostExecute方法会运行在主线程呢?为什么doInBackground方法会运行在子线程呢?子线程调用publishProgress之后为什么能进而调用到主线程的onProgressUpdate?AsyncTask的使用真的有想象中那么安全吗?
不用多想,上面的几个问题,只有源码君才能告诉我们答案,所以,今天就带领大家从底层源码的角度深入剖析AsyncTask的内部实现机制,让大家对AsyncTask的工作原理有一个透彻的理解,大家是不是有点小期待了呢_
随着Android版本的变迁,AsyncTask在任务执行方面有着较大的差异。当一开始推出时,诸多任务是在一个单个的后台线程上串行执行的。从Android 1.6(API 4)开始,任务是在一个线程池中并发执行的。从Android3.0(API 11)开始,任务又变为在一个单个的线程上串行执行。本篇文章基于Android 4.1.2的源码进行分析。
首先来看AsyncTask的构造方法,代码如下:
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
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 occured while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
AsyncTask的构造方法中做的事情不多,就是初始化了两个成员变量,一个是WorkerRunnable类型的mWorker(WorkerRunnable实现了Callable接口),一个是FutureTask类型的mFuture,并且将mWorker作为构造参数传入mFuture中(对Callable,Future,FutureTask不太了解的同学请先移步至http://www.cnblogs.com/dolphin0520/p/3949310.html。
接下来我们去看AsyncTask的execute方法:
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>Note: this function schedules the task on a queue for a single background
* thread or pool of threads depending on the platform version. When first
* introduced, AsyncTasks were executed serially on a single background thread.
* Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
* executed on a single thread to avoid common application errors caused
* by parallel execution. If you truly want parallel execution, you can use
* the {@link #executeOnExecutor} version of this method
* with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
* on its use.
*
* <p>This method must be invoked on the UI thread.
*
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
* @see #execute(Runnable)
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
execute方法竟然如此简单,仅仅是去调用了executeOnExecutor方法,我们赶紧去看一下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();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
在executeOnExecutor中,会先去判断当前AsyncTask的状态,如果不为PENDING,则抛异常。如果是PENDING,则将当前状态变为RUNNING,再去调用onPreExecute方法,由于此时的executeOnExecutor是运行在主线程的,所以onPreExecute方法也是运行在主线程的。之后再去对mWorker的mParams字段进行赋值并利用exec去执行mFuture。
我们基本可以推断出,任务执行的逻辑应该是在** exec.execute(mFuture)中,观察一下exec所对应的实参,是一个名为sDefaultExecutor**的Executor。这个sDefaultExecutor具体又是个什么样的Executor呢?看下面两行代码:
/**
* 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();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
我们发现,sDefaultExecutor其实就是一个SerialExecutor类型的常量,根据注释,SerialExecutor在执行任务时,会以一个串行的顺序,一次仅执行一个任务。由于SERIAL_EXECUTOR是一个常量,所以在一个特定的进程之内,串行化是具有全局效果的。
我们赶紧去看一下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);
}
}
}
首先,SerialExecutor中有两个很重要的成员变量,一个是ArrayDeque<Runnable>类型的mTasks,它是一个双端队列,存储了我们要执行的任务。还有一个是Runnable类型的mActive,初始值为null。
接下来就到了最为关键的execute方法了。execute方法接收的参数是一个Runnable对象,到这里,有的同学可能感觉到有点奇怪,在executeOnExecutor方法中,我们的Executor的execute方法接收的是一个FutureTask的对象,为什么两个execute方法接收的参数不匹配呢?其实是这样的,FutureTask本身就实现了RunnableFuture接口,RunnableFuture接口又继承了Runnable, Future接口,所以,FutureTask完全可以当作一个Runnable来用。我们继续去看SerialExecutor的execute方法,首先,新建了一个Runnable任务,在其run方法中有一个try,finally结构,try中直接去调用了传入的Runnable对象的run方法,finally中调用了scheduleNext方法。之后将这个新建立的Runnable任务放入双端队列的尾部。接下来会去判断mActive这个变量是否为null,若为null,执行scheduleNext方法。第一个任务到来时mActive肯定是null,所以肯定会去执行scheduleNext方法。我们再去看一下scheduleNext方法,它会从双端队列的队头取出一个元素,赋给mActive变量,如果此时的mActive变量不为null,则利用线程池THREAD_POOL_EXECUTOR来执行这个任务。
想象一下,如果在第一个任务执行的过程中,又来了第二个任务,会发生什么事情呢?首先,依然是对我们传入的Runnable任务进行重新封装,入队,之后会再去判断mActive是否为null,此时,mActive是不为null的,所以不会再去执行scheduleNext方法。那我们第二个任务就永远得不到执行了吗?其实不是的,我们回到之前的try,finally结构,我们发现,当try中的任务逻辑执行完成之后,会在finally中调用scheduleNext方法,也就是说,当我们第一个任务执行完成之后,会再去调用scheduleNext方法,在scheduleNext方法中,会从双端队列中取出第二个任务,交给线程池去执行,由此,任务的执行变成串行化了。
我们再回过头看一下SerialExecutor的execute方法,看到r.run()这一句。我们知道,execute方法的参数表面上是一个Runnable对象,实际上我们传递给它的是一个FutureTask对象,那么r.run()自然也是执行的FutureTask对象的run方法。FutureTask对象的run方法会去调用Sync内部类的innerRun方法,我们来看一下Sync内部类的innerRun方法:
void innerRun() {
if (!compareAndSetState(READY, RUNNING))
return;
runner = Thread.currentThread();
if (getState() == RUNNING) { // recheck after setting thread
V result;
try {
result = callable.call();
} catch (Throwable ex) {
setException(ex);
return;
}
set(result);
} else {
releaseShared(0); // cancel
}
}
在Sync内部类的innerRun方法中,会去调用callable的call方法。这个callable是什么呢?其实就是我们在AsyncTask构造方法中初始化的mWorker变量,我们再回顾一下mWorker的初始化代码:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
return postResult(doInBackground(mParams));
}
};
注意,此时的call方法是在子线程运行的。我们看到return postResult(doInBackground(mParams))这一句,终于,我们发现了doInBackground方法,由于当前的call方法是在子线程运行的,所以doInBackground方法也是在子线程运行的。
我们继续去看一下postResult方法:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
大家有没有感觉到这段代码很熟悉呢?没错,这就是我们Android的异步消息处理机制。首先,利用sHandler去获取一条消息,消息的what字段是MESSAGE_POST_RESULT,消息的obj字段是new AsyncTaskResult<Result>(this, result),AsyncTaskResult中封装了当前的AsyncTask任务以及需要传递的数据。之后调用message的sendToTarget方法将这条消息发送给sHandler。
我们看下sHandler的定义:
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
@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;
}
}
}
可以看到,sHandler是一个InternalHandler类型的常量。由于我们的AsyncTask只能在主线程初始化,所以sHandler在初始化时用的是主线程的Looper,其handleMessage方法自然也是运行在主线程的。在handleMessage方法中,首先取出消息的obj字段并强转为AsyncTaskResult类型,之后会去判断消息的what字段,如果是MESSAGE_POST_RESULT,则执行result.mTask.finish(result.mData[0])。其中result.mTask代表当前的AsyncTask对象,result.mData[0]代表doInBackground(mParams)的执行结果,我们继续去看一下AsyncTask的finish方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
finish方法的逻辑很简单,如果我们的任务已经通过调用cancel(boolean)方法取消了,那么会去执行onCancelled(result)方法,否则执行onPostExecute(result)方法。最后,会将当前的AsyncTask对象的状态置为FINISHED。由于之前的handleMessage方法是运行在主线程的,所以finish方法也是运行在主线程的,finish方法中的 onCancelled(result),onPostExecute(result)方法自然也是运行在主线程的。
在handleMessage中,还有一种what字段为MESSAGE_POST_PROGRESS的消息,那么什么时候会收到这种类型的消息呢,猜一下也知道,应该是我们调用publishProgress的时候。我们去看一下publishProgress的源代码:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
当任务尚未取消时,会向sHandler发送一条what字段为MESSAGE_POST_PROGRESS,obj字段为new AsyncTaskResult<Progress>(this, values)的消息。回到handleMessage方法,因为handleMessage方法是运行在主线程中的,所以onProgressUpdate方法也是运行在主线程中的。
好了,AsyncTask的源码到这里已经基本分析完毕了,下面向大家介绍的是AsyncTask使用的缺陷问题。
可能有同学会想,AsyncTask这么牛X的一个工具,能有啥缺陷?
其实,AsyncTask还真的有缺陷,而且,这个缺陷和Android的版本息息相关,在某些Android版本下,如果AsyncTask使用不慎,甚至有可能造成我们的应用程序崩溃。
前面我们曾经提及,从Android 1.6(API 4)开始,任务是在一个线程池中并发执行的。从Android3.0(API 11)开始,任务又变为在一个单个的线程上串行执行。问题就出在这个并发执行上,这个并发执行所使用的线程池,最大支持128个任务的并发,10个任务的等待。也就是说,同时执行138个任务是没问题的,但是同时执行139个任务则会抛出异常:java.util.concurrent.RejectedExecutionException。而在Android 1.6之下,Android3.0及其之上的版本中,所有的任务都是串行执行的,同时执行再多的任务都不会有问题。
看到这里,相信大家已经对AsyncTask的底层原理有了一个较为深入的理解了,想不到小小的AsyncTask的内部竟然隐藏着如此美妙的天地,着实值得我们去探索与回味啊~~~
参考:
http://blog.csdn.net/lmj623565791/article/details/38614699
http://blog.csdn.net/guolin_blog/article/details/11711405
http://www.cnblogs.com/dolphin0520/p/3949310.html