Android框架层 —— AsyncTask源码分析

基础了解

介绍

在Android中实现异步任务机制有两种方式:Handler和AsyncTask。

Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更新,这种方式对于整个过程的控制比较精细,但也是有缺点的,例如代码相对臃肿,在多个任务同时执行时,不易对线程进行精确的控制。

为了简化操作,Android提供了工具类.AsyncTask,它使创建异步任务变得更加简单,不再需要编写任务线程和Handler实例即可完成相同的任务,但其内部也是使用Handler来传递消息,而且基于线程池。因此明显的AsyncTask比Handler要重量级。

功能总结:

  • 能定义以下执行过程的操作:预执行、执行后台任务、执行进度反馈、执行完毕
  • 对于以上功能,可以提供友好方便的API供开发人员调用
  • 可以停止任务的执行

涉及的主要类

  • AsyncTask将要介绍的主角,这是一个抽象类
  • Params:泛型类,是启动任务执行的输入参数类型
  • Progress:泛型类,是后台任务完成的进度值的类型,如进度百分比
  • Result:泛型类,是后台任务完成后的返回值类型

 

涉及的主要方法

  • doInBackground(Object[] params):继承该类唯一必须要实现的方法,做一些耗时操作
  • onPreExecute():耗时任务执行前的一些操作,运行在UI线程
  • publishProgress ():doInBackground()方法中进行调用,当这个方法被调用,会去执行onProgressUpdate()方法
  • onProgressUpdate(Object[] values):publishProgress()被调用时执行,用来实现进度的更新,运行在UI线程
  • onPostExecute(Object o):任务结束后的操作,运行在UI线程
  • onCancelled():取消执行

如何使用

步骤

1、新建一个类MyAsyncTask(随意命名),继承AsyncTask

2、实现其中的doInBackground()方法,来进行耗时的操作,这个是必须实现的;然后,根据需要选择实现onPreExecute()onProgressUpdate()onPostExecute()方法,它们的功能在前面已有介绍。

3、最后调用execute()方法,来开启这个异步任务。

注意事项

  • 异步任务的实例必须在UI线程中创建。
  • execute(Params… params)方法必须在UI线程中调用。
  • 不能在doInBackground(Params… params)中更改UI组件的信息。
  • 一个任务实例只能执行一次,如果执行第二次将会抛出异常。

代码示例

对应上面第一、二步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
protected void onPreExecute() {
// Good for toggling visibility of a progress indicator
progressBar.setVisibility(ProgressBar.VISIBLE);
}
protected Bitmap doInBackground(String... strings) {
// Some long-running task like downloading an image.
Bitmap = downloadImageFromUrl(strings[0]);
return someBitmap;
}
protected void onProgressUpdate(Progress... values) {
// Executes whenever publishProgress is called from doInBackground
// Used to update the progress indicator
progressBar.setProgress(values[0]);
}
protected void onPostExecute(Bitmap result) {
// This method is executed in the UIThread
// with access to the result of the long running task
imageView.setImageBitmap(result);
// Hide the progress bar
progressBar.setVisibility(ProgressBar.INVISIBLE);
}
}

第三步:

1
2
3
4
5
public void onCreate(Bundle b) {
new MyAsyncTask().execute("http://images.com/image.jpg");
}

源码分析

流程分析

在分析源码时,针对于这种框架层源码,我采用流程分析法,也就是从我们最先开始调用的方法开始分析,逐步探寻里面是如何调用其它对象和其它方法的。

还有一种方法是直接打开AsyncTask类,查看里面的方法、字段等,不过容易让人摸不着头脑,不容易搞清楚流程,所以这里我还是先找到一个入口,然后顺着这个入口去分析。

毫无疑问,对于AsyncTask,我们最先调用的就是它的execute()方法,代码如下:

1
2
3
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}

只有一行代码,就是调用了executeOnExecutor()方法,我们进入这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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初始化是PENDING状态的,所以首先设置当前AsyncTask的状态为RUNNING。从上面的switch也可以看出,每个异步任务在完成前只能执行一次,否则报错。
  • 然后执行onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
  • 将我们传入的参数赋值给mWorker.mParams
  • 调用exec.execute(mFuture)

前两步没什么可说的,很容易理解,重点是最后两步,我们先分析mWorker.mParamsmWorker在代码中的声明如下:

1
private final WorkerRunnable<Params, Result> mWorker;

很明显,我们接下来要看看WorkerRunnable这个类是如何定义的:

1
2
3
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}

它是一个抽象类,实现了Callable接口,且包含一个mParams用于保存我们传入的参数,下面看一下它的初始化过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
};

mWorker在构造方法中完成了初始化,可以看到在这里new了一个实现类,实现了call方法,call方法中设置mTaskInvoked=true,且最终调用doInBackground(mParams)方法,doInBackground(mParams)由开发者来实现,然后返回Result值作为参数给postResult()方法,下面进入postResult():(注意:这段代码是初始化,但是这个call()方法并不是在这里调用的,不过我们这里先把这一段的代码逻辑给讲了,后面的讲解会和这里衔接上)

1
2
3
4
5
6
7
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}

可以看到postResult中出现了我们熟悉的异步消息机制,AsyncTaskResult就是一个简单的携带参数的对象,最终通过postResult将结果投递给UI线程。

看到这一步,一定会想到在某处肯定存在一个Handler,且复写了其handleMessage方法等待消息的传入,以及消息的处理。所以我们进入getHandler()方法进行查找,最终找到代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());
}
@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;
}
}
}

对于mWorker.mParams = params方法就分析到这,这行代码的作用就是获得从UI现存传递来的参数,下面开始分析exec.execute(mFuture)方法:

1
2
3
public interface Executor {
void execute(Runnable command);
}

进入execute(mFuture)发现这是一个接口,所以我们需要找到它的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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对象,我们直接看execute()方法,它有两步:

  • 第一步: 封装任务(目的是让任务串行),然后放入队列。新建一个Runable包裹提交进来的任务,在try作用域里面先执行r.run(),必须等任务执行完后(或者会执行失败也算是‘执行完’),要保证队列中的其他任务执行,所以在finally作用域中会执行 scheduleNext()调度下一个任务。所以任务是串行执行的。最后我们会把这个新建的Runnable对象放入队列mTasks中。

  • 第二步:查看是否有可立即执行的任务mActive。 如果有执行的,则立即放入THREAD_POOL_EXECUTOR线程池调度器执行。

关于ThreadPoolExecutor这里不做介绍,下面直接进入到ThreadPoolExecutor的execute()方法,如下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

这段代码的主要功能是将异步任务mFuture加入到将要执行的队列中,重要的函数为addWoker(),这里就不再进入讲解了。需要说明的是,这个线程池是一个静态变量;那么在同一个进程之内,所有地方使用到的AsyncTask默认构造函数构造出来的AsyncTask都使用的是同一个线程池。

如果App模块比较多并且不加控制的话,那么超过了工作队列以及线程数目的限制导致这个线程池发生阻塞,那么悲剧发生,默认的处理方式会直接抛出一个异常导致进程挂掉。假设你自己写一个异步图片加载的框架,然后用AsyncTask实现的话,当你快速滑动ListView的时候很容易发生这种异常,这也是为什么各大ImageLoader都是自己写线程池和Handlder的原因。

看到这里,我们发现接连使用了两个调度器,一个是SerialExecutor的execute()方法,一个是ThreadPoolExecutor的execute(),为什么要用两个调度器呢?

那是因为它们负责的功能不同,从我们上面对SerialExecutor的分析可以看出,SerialExecutor是为了保证任务被一个一个的取出执行,也就是说AsyncTask中任务是串行执行的,只有一个AsyncTask实例执行结束,才能去调度另一个。我们从SerialExecutor的实例是个静态变量这一点也可以看出。那THREAD_POOL_EXECUTOR线程池调度器则是根据CPU数构造线程Thread缓存池,节约资源,提高AsyncTask执行性能。

也许你不理解,为什么AsyncTask默认把它设计为串行执行的呢?

由于一个进程内所有的AsyncTask都是使用的同一个线程池执行任务;如果同时有几个AsyncTask一起并行执行的话,恰好AysncTask的使用者在doInbackgroud里面访问了相同的资源,但是自己没有处理同步问题;那么就有可能导致灾难性的后果!

由于开发者通常不会意识到需要对他们创建的所有的AsyncTask对象里面的doInbackgroud做同步处理,因此,API的设计者为了避免这种无意中访问并发资源的问题,干脆把这个API设置为默认所有串行执行的了。如果你明确知道自己需要并行处理任务,那么你需要使用executeOnExecutor(Executor exec,Params… params)这个函数来指定你用来执行任务的线程池,同时为自己的行为负责。(处理同步问题)

讲完exe.execute(mFuture),其中的参数mFuture我们还没讲,下面看看它:

1
private final FutureTask<Result> mFuture;

mFuture是FutureTask的一个实例,它的初始化也是在AsyncTask的构造函数中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}
};

简单来说,mFuture包装了这个mWorker对象,在这个mFuture的run函数(文中没有给出这部分代码)中又会调用mWork对象的call方法,在call方法中又调用了doInBackground()方法。因为mFuture提交给了线程池来执行,所以,使得doInBackground()执行在非UI线程。得到doInBackground()的结果后,通过postResult()传递结果给UI线程。这一段逻辑我们已经在开始讲过了,这样就可以和前面连接起来了。

而且我们可以看到,mFuture实例对象的done()方法中,如果捕捉到了CancellationException类型的异常,则发送一条“MESSAGE_POST_CANCEL”的消息;如果顺利执行,则发送一条“MESSAGE_POST_RESULT”的消息,而消息都与一个sHandler对象关联。

设计模式

  • 模版方法模式

AsyncTask中用到的一个很明显的设计模式就是模版方法模式,一个execute()方法中封装了onPreExecute()doInBacground()onPostExecute()这几个流程,用户可以根据自己的需求再复写这几个方法。这个模式理解起来很简单,就不再做太多介绍。

疑惑

  • 既然AsyncTask的任务是串行执行的,也就是一个任务一个任务的执行,那使用线程池的意义又是什么呢?希望对这个有理解的同行可以在我的简书下留言,确实是不解。

参考: