AsyncTask 是一种轻量级异步任务类,可以在线程池执行后台任务,获取到的结果传递给主线程并且在主线程中更新 UI。AsyncTask 比较适合执行短时间任务,对于长时间任务推荐使用 Executor,ThreadPoolExecutor 和 FutureTask。
AsyncTask 是一个抽象类,提供三个泛型参数,分别是 Params,Progress 和 Result;以及 4 个步骤:onPreExecute,doInBackground,onProgressUpdate 和 onPostExecute。
AsyncTask 三个泛型参数
Params:发送给执行任务的参数类型。
Progress:执行后台任务进度的类型。
Result:执行完后台任务返回的结果类型。
AsyncTask 四个核心方法
onPreExecute():在任务执行之前调用,主线程执行;主要做一些初始化工作,比如在用户界面展示进度条。
doInBackground(Params…):onPreExecute() 执行完成后被调用,在线程池执行;所有的异步操作都在这个方法执行,执行结果被返回时,onPostExecute(Result) 会被调用。如果在该方法中调用 publishProgress(Progress…),那么方法 onProgressUpdate(Progress…) 也会被调用,主要用于更新后台任务进度。
onProgressUpdate(Progress…):publishProgress(Progress…) 执行完之后被调用,在主线程执行;主要在用户界面显示后台任务执行进度。
onPostExecute(Result):doInBackground(Params…) 执行完之后调用,在主线程执行;参数 Result 是 doInBackground(Params…) 的返回值。
一个异步任务可以通过调用 cancel(boolean) 随时取消,此时 isCancelled() 被调用,这就导致 doInBackground(Params…) 执行完后 onPostExecute(Result) 不会被调用。
在使用 AsyncTask 的过程中,要注意以下几点:
AsyncTask 类必须在 UI 线程加载,Android 4.1 已经自动绑定了。
AsyncTask 实例必须在 UI 线程创建。
execute(Params…) 必须在 UI 线程调用。
不要手动调用 onPreExecute()、onPostExecute(Result)、doInBackground(Params…)、onProgressUpdate(Progress…)。
一个 AsyncTask 对象只能被执行一次;否则会抛异常。
以上是 AsnycTask 基本知识点,掌握知识点后就要学会如何使用它。那么接下来就来学习 AsyncTask 用法。
AsyncTask 用法
AsyncTask 是抽象类,不能直接实例化,必须创建新类并继承它,抽象方法 doInBackground(Params…) 是一定要重写的,其它三个方法根据自己的需求确定。以下通过 URL 获取数据为例子来讲解 AsyncTask 的用法。代码如下:
public class AsyncTaskExample extends AsyncTask<String, Integer, String> {
@Override
protected void onPreExecute() {
super.onPreExecute();
mLoad.setVisibility(View.VISIBLE);
}
@Override
protected String doInBackground(String... params) {
return getUrlResponse(params[0]);
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
mLoad.setVisibility(View.GONE);
mText.setText(s);
}
}
从代码中可以很清晰地看出,第 5 行是显示加载进度条,表示正在获取数据;第 10 行是核心代码,异步操作,网络请求数据并将结果返回;第 16 - 17 行代码主要操作是隐藏进度条,表示数据加载完毕,并将获取到的结果显示出来。这里主要给出核心代码,至于其它代码也就调用而已。
那么该如何调用呢?很简单,一行代码就搞定
new AsyncTaskExample().execute(url);
AsyncTask 源码解析
知其然必知其所以然。对于新知识点,学会使用之后,就应该探究其原理。由于个人倾向于通过画图来理解知识点的流程,因此先简单地给出 AsyncTask 任务执行的流程图,再根据流程图和源码进行讲解。流程图如下:
对于源码的理解,一般是以最终调用的方法为入口,一步一步地理解整个流程。那么对于 AsyncTask 该从哪里入手呢?当然是从方法 execute(Params… params) 入手了,代码如下:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
对于 AsyncTask 不同版本,execute(Params… params) 方法的执行方式是不一样的。Android 1.6 以前,AsyncTask 是单线程串行执行任务的;Android 1.6,AsyncTask 是线程池多线程并行执行任务;但是到 Android 3.0,AsyncTask 又改为单线程串行执行任务的。该方法的逻辑很简单,直接调用方法 executeOnExecutor(Executor exec, Params… params),将我们传入的参数 params 和 sDefaultExecutor 传到该方法里,并将的返回值返回。那么来看下该方法的具体实现,代码如下:
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(Executor exec, Params… params) 方法是在线程池 THREAD_POOL_EXECUTOR 执行,允许多任务并发执行,但是不推荐采用多任务并发执行;在主线程执行。该方法实现的主要功能是:
检查任务状态,并记录任务当前状态;
调用 onPreExecute() 方法,根据我们自己的需求可以重写该方法;
将我们传入的参数 params 赋值给 WorkRunnable 中字段 mParams(稍后解释);
调用 SerialExecutor 中方法 execute(Runnable r) 执行任务。
mWorker 是 WorkerRunnable
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;
}
};
该回调方法实现的主要功能:
将 mTaskInvoked 设置为 true,表示任务已经被调用过;
设置线程优先级为后台线程;
调用 doInBackground(mParams) 方法,异步执行,后台执行的逻辑都写在这个方法里面,一定要被重写;如果任务执行抛出异常时,取消任务;
调用 postResult(result) 方法;
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;
}
从代码里可以看出,将执行结果通过 sHandler 发送 MESSAGE_POST_RESULT 的消息,然后 handleMessage() 方法收到消息后进行相应的处理。sHandler 是 InternalHandler 实例,主要作用是将任务执行的环境从线程切换到主线程中,从 InternalHandler 的具体实现就可以看出了。代码如下:
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:
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
从构造函数 InternalHandler() 就可以看出了,获取主线程 Looper,而 Handler 必须与 Looper 进行绑定,因此可以断定是在主线程里。handleMessage()
函数对两种消息进行处理:MESSAGE_POST_RESULT 和 MESSAGE_POST_PROGRESS;而我们刚刚发送的消息是 MESSAGE_POST_RESULT,那就先来看该消息收到后会做什么处理吧?很显然,调用 finish(Result result),具体实现如下:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
如果任务被取消了,直接调用 onCancelled(result) 方法,onPostExecute(result) 方法不会被调用;否则就调用 onPostExecute(result) 方法,该方法需要被重写,在主线程执行,根据返回的结果进行相应的处理;最后修改任务的状态。那么对于消息 MESSAGE_POST_PROGRESS 是从哪里发出来的呢?还记得在前面的知识点讲解中有提到过如果在 doInBackground(mParams) 方法中调用 publishProgress(Progress…) 方法时,方法 onProgressUpdate(Progress…) 也会被调用,用于后台任务进度更新。没错,消息 MESSAGE_POST_PROGRESS 就是用来处理进度更新的。先看下 publishProgress(Progress…) 具体实现:
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
很显然,如果任务没有被取消的话,就会发送消息 MESSAGE_POST_PROGRESS,那么来看下收到该消息后的处理逻辑,即调用 onProgressUpdate(Progress… values) 方法,该方法需要我们根据自己的需求进行重写。
再回到 executeOnExecutor(Executor exec, Params… params) 方法,第 14 行代码开始执行任务,在理解如何执行任务之前,先来理解参数 mFuture 和 sDefaultExecutor 的含义。
mFuture 是 FutureTask 实例,在 AsyncTask 构造方法中初始化。将 mWorker 作为参数传入 FutureTask 构造函数,个人认为传入该参数的作用是由于 FutureTask 中 run() 方法会被调用,而在该方法里会通过传入参数 mWorker 调用 call() 方法,进而使任务得到执行。FutureTask 是一个并发执行任务类,可以执行任务、取消任务、查询结果、获取结果;提交到线程池执行。实现的接口有 Future、Runnable。
对于传入的参数 sDefaultExecutor,究竟是什么啥玩意呢?让我们来探个究竟吧。sDefaultExecutor 是 SerialExecutor 的实例,而 SerialExecutor 实际上是一个串行的线程池,主要的功能是一个进程中所有的 AsyncTask 任务都在这个串行的线程池中排队执行。看到这里,是不是还不知道任务真正在哪里被开始执行?其实以上都只是铺垫,下面才真正拉开序幕。真正开始执行任务的逻辑是在 SerialExecutor 中 execute(Runnable r) 方法里,具体实现如下:
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);
}
}
}
首先将 AsyncTask 通过线程池 SerialExecutor 添加到队列里(从这里可以看出 SerialExecutor 的作用),然后重写 run() 方法,并判断 mActive 是否为 null,即当前是否有任务在执行,如果有任务执行的话就等待该任务执行完后再执行其他任务,否则就执行任务,即调用 scheduleNext() 方法,该方法的主要功能是从队列 mTasks 获取任务,任务不为空的话就直接提交到线程池 THREAD_POOL_EXECUTOR 里执行(任务真正开始执行),即启动任务,根据个人的理解,任务被启动后,会调用第 6 行代码,即 run() 方法,进而调用 FutureTask 中 run() 方法,从而会调用 WorkerRunnable 中 call() 方法,因此任务被执行,我们重写的方法也会被调用。结合以上流程图应该能更清晰地理解 AsyncTask 执行流程。
以上是自己在学习 《Android 开发艺术探索》 这本书第十一章关于 AsyncTask 这个主题的学习笔记,由于自己能力有限,有错误的地方欢迎指出。
参考资料
https://developer.android.com/reference/android/os/AsyncTask.html
《Android 开发艺术探索》》中 第 11 章 Android 的线程和线程池