在Android中,如果需要执行一个较短的耗时任务(几秒钟内),比如图片下载,数据库访问等,我们通常会使用AsyncTask来执行。如果你对AsyncTask如何使用不是特别了解,可以看看AsyncTask简单实例。
使用AsyncTask来处理异步任务给大家带来了很多便利,你不需要操作Thread和Handler,就能很方便的在工作线程和UI线程中做自己想要做的事情。那么AsyncTask是如何做到的呢?本文就以Android 7.0这个版本的源码来分析一下AsyncTask的工作原理。
在分析代码之前,我们先回想一下我们一般是如何使用AsyncTask的。一般步骤如下:
- 继承AsyncTask实现它的子类,然后生成一个AsyncTask对象
- 在UI线程执行execute()
- 在UI线程onPreExecute()中做一些初始化操作,比如说显示进度条什么的
- 在工作线程中调用doInBackground()执行异步耗时任务,如果需要获取进度信息,那么在doInBackground()中调用publishProgress(),此时onProgressUpdate()就会被回调
- 在UI线程中调用onPostExecute()进行后处理,比如下载图片后将它显示在ImageView上
- 可以调用cancel来取消任务,如果取消成功,则会调用onCancelled()
现在我们就以这个顺序来看看源码实现。
1.AsyncTask的任务生成
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//这里细节先不看
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
//这里细节先不看
}
};
}
这里主要初始化了两个变量,一个是WorkerRunnable变量,另一个是FutureTask变量。其中FutureTask是Future接口的一个默认实现,通过它可以执行任务,并且可以查看任务执行结果等。如果不是很清楚的话,可以看看Runnable,Callable,Future和FutureTask简介。那么什么是WorkerRunnable呢?我们先看看定义。
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
从这里我们可以看出它是一个抽象类,跟Callable接口的区别就是多了一个Params[]成员变量。
2. AsyncTask的任务执行
@MainThread//注解表明需要在UI线程执行这个函数
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);//sDefaultExecutor等一会看它的实现
}
很简单,直接调用executeOnExecutor(sDefaultExecutor, params)函数,那么我们进入这个函数看看。
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {//初始化AsyncTask时状态为PENDING,执行过程中为RUNNING,结束任务为FINISHED。
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();//注意看这里,好熟悉的身影,对了,就是在这里做一些初始化动作,比如显示进度条等,很显然,也需要运行在UI线程
mWorker.mParams = params;//这里,把运行这个任务的参数赋值给了mWorker,那么mWorker是什么,如果你忘了,那还是去看看构造函数。。。,它就是个Callable对象,只不过多了个mParams成员而已。
exec.execute(mFuture);//exec就是sDefaultExecutor, mFuture就是个FutureTask,
return this;
}
从上面的代码看出,一个AsyncTask变量(代表一个任务)只能执行一次,因为如果这个任务在执行中或者执行结束,那么再运行这个任务,就会抛出异常了。
我们继续往下看exec.execute(mFuture)。mFuture先不管,就把它当做一个任务好了。先看看exec,也就是sDefaultExecutor的实现。找啊找。。。嘿呦嘿呦,找到了,定义如下:
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();//保存任务的队列哦
Runnable mActive;//现在运行的任务
public synchronized void execute(final Runnable r) {//exec.execute(mFuture)调用的就是这个函数啊,不对啊,它是Future,我是Runnable,你确定可以吗?当然是可以的,因为FutureTask也实现了Runnable接口,万能的多态啊,I love it。
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);//直接使用线程池运行这个任务,那么这个任务是什么?我们还是要回到AsyncTask的初始化部分,在接下来会分析,客官,骚等。。。
}
}
}
上面的这段代码主要实现了任务如何入队,以及什么时候执行的问题。execute(final Runnable r)先将当前的任务放在任务队列中,这个时候有两种情况:一种是第一个任务进来时,先将这个任务入队,由于mActive == null,此时从队列中取出这个任务直接运行。另外一种就是其他任务进来时,由于mActive已经不为空了,那么execute只会做入队操作。那么其他任务什么时候执行呢?看看上面的代码finally部分,它的意思就是说只有一个任务完成以后,下一个任务才会开始执行。目的就是为了实现每个时间点只有一个任务在执行,其他任务只能等待。这也是为什么AsyncTask一直强调比较适合在任务耗时比较小的情况下使用的原因,因为如果这个任务耗时比较长,那么其他任务只能等待。
3.FutureTask的任务实体
前面代码已经看了一大坨了,还是先整理整理思路。主要做了这些事情:
- AsyncTask的初始化,主要是初始化了mWorker和mFuture,其中mWorker是Callable对象,mFuture是FutureTask
- 然后调用execute(params),将params赋值给了mWorker,并通过一个执行器执行mFuture这个任务
- 这个执行器实现了每个时刻只运行一个任务,一个任务完成了再执行下一个任务
通过梳理,我们已经大致知道一个AsyncTask的调度过程,现在来看看一个任务主要做了哪些事情。在AsyncTask里初始化时,我们将相关代码先忽略了,现在因为要看实际的运行任务,是时候把它拿出来看看了。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);//设置这个任务在执行中
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);//设置为后台线程
//noinspection unchecked
Result result = doInBackground(mParams);//先注意mParams,这个参数我们在调用execute时候已经给它赋值了。由于整个任务运行在执行器中,所以doInBackground是运行在工作线程中的。
Binder.flushPendingCommands();
return postResult(result);//运行结束后将这个结果发送出去。
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {//在这个futureTask运行结束后会回调这个函数
try {
postResultIfNotInvoked(get());//任务结束后,通过future的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);
}
}
};
}
首先需要明白的是,一个FutureTask的任务实体是mWorker。如果不是很明白,可以去看看Runnable,Callable,Future和FutureTask简介后面的FutureTask的代码分析。在FutureTask的run()函数里会直接取出callable,然后执行callable实体的call()函数。在call()函数中,我们调用了doInBackground(),并且将这个函数的执行结果出去了。至于如何发送的,请看看下文。
4.AsyncTask任务结果显示和进度信息获取
现在我们来看看postResult(result)的代码实现
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对象,把它发送出去。其中AsyncTaskResult包括了this(AsyncTask)和result(任务执行结果)。由于这个结果是通过Handler发送的,
那我们看看Handler的代码实现,代码如下:
private static class InternalHandler extends Handler {
public InternalHandler() {
super(Looper.getMainLooper());//这是UI线程的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]);//result.mData[0],这个就是任务的执行结果
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);//
break;
}
}
}
在这里调用了AsyncTask的finish方法,我们去看看吧。
private void finish(Result result) {
if (isCancelled()) {//如果被取消了
onCancelled(result);
} else {
onPostExecute(result);//正常情况下
}
mStatus = Status.FINISHED;//任务状态置为结束
}
这段代码信息量蛮大的,我们可以看到onCancelled(result)和onPostExecute(result)回调,这两个回调都在UI线程中执行。如果一个任务被取消了,那么只有onCancelled()会被回调,否则,如果任务顺利完成了,那么只会执行onPostExecute()回调。不管是任务取消了,还是任务完成了,任务状态都会被置为完成。
至此,我们大致已经了解AsyncTask的内部实现。现在还有一个经常会用到的onProgressUpdate()回调接口,我们知道,如果需要获取进度,我们需要在doInBackground()调用publishProgress()函数。
@WorkerThread//在工作线程中,也就是说需要在doInBackground()中调用
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {//如果没有被取消
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();//生成AsyncTaskResult对象,将进度信息发送给消息队列,然后在handleMessage()里处理。
}
}
InternalHandler的实现
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);//获取到进度信息
break;
5.AsyncTask代码总结
现在我们简单梳理一下AsyncTask的源码:
1. AsyncTask的初始化,主要是初始化了mWorker和mFuture,其中mWorker是任务实体,mFuture可以对任务进行管理,如取消,获取结果等。
2. 然后调用execute(params),将params赋值给了mWorker,并通过一个执行器执行mFuture这个任务
3. 这个执行器实现了每个时刻只运行一个任务,一个任务完成了再执行下一个任务,任务不会并发执行
4. 这个执行器最终执行任务是通过线程池来完成的
5. 在mWorker里,调用了doInBackground,我们可以在这个函数里实现耗时任务。任务执行结束后,调用postResult(result),通过handler将运行结果发送到UI线程
6. 在UI线程根据执行结果来调用onPostExecute()
7. 如果在doInBackground中调用了publishProgress,那么会回调onProgressUpdate
8. 如果你想任务并行执行的话,那么调用executeOnExecutor(Executor exec,
Params… params),其中exec可以自己定义