AsyncTask 使用&源码分析
前言:AsyncTask 在 Android 日常开发中非常常见,如果你没用过那你一定要学习一下,用过的话了解它的内部原理也是非常重要,可以帮助我们更好的更规范的使用它。
1、AsyncTask 的使用
是什么?
一个封装了线程池和 Handler 的类。
有什么用?
想象一下我们要进行一项耗时操作并在结束时更新UI,一般会使用【Thread + Handler】。而使用 AsyncTask 就可以优雅的实现,它有两个优点:1、它是线程池实现的,所以免去了频繁创建和销毁线程带来的系统开销,提高了效率 2、结构简单,代码高内聚。
怎么用?
首先它有三个范型参数:Params, Progress, Result。
- Params。代表这项执行这项任务所需要的参数。
- Progress。代表这项任务的执行进度。
- Result。代表这项任务的处理结果。
其次,它还有几个可以供我们使用的方法:
- onPreExecute()。主线程运行,可以在这个方法中更新UI来提示将要开始执行任务了。
- doInBackground(Params... params)。这个方法是抽象方法,它会在线程池中运行,所以我们可以在这个方法里面去执行耗时操作。
- onProgressUpdate(Progress... values)。主线程运行,我们可以在这个方法中来更新 UI 来提示用户任务的进度。当 publishProgress(Progress... values) 方法被调用的时候并且该任务没有被取消,就会调用该方法。
- onPostExecute(Result result)。主线程运行,当耗时任务结束时就会调用该方法,可以在这个方法中来更新 UI 来提示任务的执行结果。
- onCancelled(Result result) 与 onCancelled()。主线程运行,当任务被取消的时候就会调用。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
这是官方给出使用 AsyncTask 的示例,通过它可以下载东西并且更新UI。可以看到这里创建了一个 DownloadFilesTask 子类继承 AsyncTask ,然后重写了 doInBackground() ,onProgressUpdate(),onPostExecute() 这三个方法。
在 doInBackground 中,拿到 urls 去下载东西,然后通过调用 publishProgress() 方法来触发 onProgressUpdate() 去更新UI,这里它设置了一下进度。注意这里有个 isCancelled() 判断,如果任务被取消了,那么它就会停止任务,虽然这里依然 return totalSize,但是它不会再去调用 onPostExecute() 了。
可以通过 execute() 来执行一个任务:
new DownloadFilesTask().execute(url1,url2,url3);
在使用 AsyncTask 时要注意以下几点,原因会在下面进行分析:
- 在使用 execute() 时,必须要在主线程。
- AsyncTask 的对象需要在主线程创建。
- 在 Android3.0 之后 AysncTask 任务的执行默认是异步的,如果想要同步执行可以使 executeOnExecutor()。
- 一个任务只能被执行一次,也就是说一个 AsyncTask 只能调用一次 execute() 或 executeOnExecutor()。
- 用 AsyncTask 执行短耗时操作时极好的,但是长耗时就不推荐了,长耗时可以使用线程池。
2、源码分析(基于 API28)
下面我们将对 AsyncTask 的源码进行分析。首先从 AsyncTask 的 execute() 方法入手。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
可以看到,当我们调用 execute(Params... params) 方法的时候可以传入参数 params,这是可变参数,可以理解为数组,要求数据类型要一致。然后它会去调用 executeOnExecutor((sDefaultExecutor, params) 方法。这里多了一个 sDefaultExecutor 参数,让我们看看它是什么。
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new 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);
}
}
}
可以看到 sDefaultExecutor 是一个实现了 Executor 接口的类的对象,SerialExecutor 从字面上可以理解为串行执行器,就是它控制了 AsyncTask 是串行执行任务的,具体执行任务的流程我们等会儿再分析。我们接着看 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;
}
这里先判断了一下 mStatus 的状态,如果这个任务已经被执行过了或者已经结束了,再去执行一次就会抛出异常。这解释了为什么 AsyncTask 只能够执行一次 execute 。
然后它会把状态设为 RUNNING ,然后去执行 onPreExecute() 方法,这里出现我们可以重写的第一个方法,我们可以在这个方法里面做开始执行任务之前要做的工作,比如更新一下UI。那我们也就可以理解为什么 execute() 要在主线程执行了,如果在子线程执行,那么就不能更新 UI 了,也就与设计这个方法的初衷相违背了。
接着出现了 mWorker 和 mFuture,看看它们是什么东西。
private final WorkerRunnable<Params, Result> mWorker;
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
可以看到 mWorker 是一个实现了 Callable 接口的抽象类,它拥有一个 mParams。看看 Callable 是什么东西。
public interface Callable<V> {
V call() throws Exception;
}
可以看到 Callable 就是一个接口函数,里面有个 call 方法。接着我们再去看看 mFuture 是什么东西。
private final FutureTask<Result> mFuture;
FutureTask 在这里我们可以把它理解为 一个 Runnable。
接着我们看看 mWorker 和 mFuture 这两个对象在是在哪里初始化的。
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
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 (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);
}
}
};
}
这是 AsyncTask 的构造方法,当我们初始化 AsyncTask 对象的时候,会调用 AsyncTask 的构造方法。可以看到,这里有三个对象的初始化,分别是 mHandler,mWorker,mFuture。
调用构造方法时,这个构造方法的参数 callbackLooper 一定是空,那么就调用 getMainHandler () 去获得主线程的 Handler,如下代码所示。可以看到这个 sHandler 获得了主线程的 Looper ,也就说明了我们的 AsyncTask 对象在子线程和主线程创建的效果其实是一样的(但是为了兼容其他 API,我们还是需要在主线程创建对象)。
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
在构造方法接着可以看到创建了一个 WorkerRunnable 的匿名内部类对象给 mWorker,这里面能够看到我们的第二个方法 doInBackground(mParams),这个 mParams,就是我们前面赋值的,在 doInBackground() 中我们可以执行耗时操作,因为它是在线程池中执行的,后面会继续分析。
下面接着创建了一个 FutureTask 的匿名内部类对象给 mFuture,可以看到它把 mWorker 组装进来了。
现在我们可以继续分析当执行完 onPreExecute() 后,它做了什么呢?
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
可以看到,这边把我们传进去的 params 传给了 mWorker。然后 exec.execute(mFture),这里的 exec 就是上面的 sDefaultExecutor ,也就是串行执行器,为了方便阅读,我再贴出这个串行执行器的代码,细细分析 execute()到底做了什么。
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);
}
}
}
可以看到,这里先创建了一个队列,范型是 Runnable,当我们执行 execute 时,会调用 offer 方法把 Runnable 对象压入队列,这个 Runnable 对象保存了我们的 mFuture 对象。
接着下面判断如果当前没有任务再执行(mActive == null) ,就调用 scheduleNext() 方法。如果当前有任务正在执行,execute()方法到这里就结束了,也就是说如果有任务正在执行,入个队列就完事了。
再看看 scheduleNext() 方法,它首先会从任务队列中取出第一个任务,如果存在任务,就把这个任务放到 THREAD_POOL_EXECUTOR (线程池)中去执行。当这个任务执行完的时候,一定会进到 finnally 语句块,然后又去执行了 scheduleNext(),也就是说再去队列中取出任务去执行。看到这里也就明白了,AsyncTask 的任务处理是串行的了。
接着看 r.run() 做了什么,这里的 r 就是我们前面组装的 mFture 对象,看看它的 run 做了什么事情。
public void run() {
...
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
}
...
}
}
...
}
上面的 callable 就是我们传进去的 mWorker 对象,可以看到它触发了 mWorker 的 call() 方法。
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;
}
};
这里的 call() 方法被触发时,就会看到我们熟悉的 doInBackground(),这下我们能理解为什么 doInBackground() 是在子线程中运行的了,因为这个任务是在 THREAD_POOL_EXECUTOR 线程池中执行的。执行完 doInBackground() 会得到一个 result,在 finally 语句中,通过 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 的同学应该很熟悉。
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;
}
}
}
我们传入的 msg.waht 是 MESSAGE_POST_RESULT,它会去执行 finsih,再看看 finish 做了什么。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
可以看到这里进行了判断,如果任务被取消了,就会去执行我们的 onCancelled() 方法。如果任务没有被取消就会去执行我们的 onPostExecute() 方法。通过 Handler 将子线程任务的执行结果又切换回了主线程,我们可以在这两个方法中更新UI。至此我们算是走完 AsyncTask 的全部流程。
经过源码分析我们明白了前面提出的在使用 AsynsTask 过程中需要注意的问题。并且现在我们也可以解释为什么使用 executeOnExecutor(THREAD_POOL_EXECUTOR) 可以并发执行。原因就是 executeOnExecutor(THREAD_POOL_EXECUTOR) 没有使用串行执行器,THREAD_POOL_EXECUTOR 本身是具有执行并发任务的能力的。只是我们在调用 execute() 时,还使用中利用串行执行器将队列里的一个个任务排队分发给 THREAD_POOL_EXECUTOR 去执行而已。我们在使用 executeOnExecutor(THREAD_POOL_EXECUTOR) 的时候还要注意 THREAD_POOL_EXECUTOR 线程池中队列的最大容量,若队列中的任务数超过里最大容量,就会抛出异常。
同时对于较长的耗时操作,不要使用 AsyncTask,可以使用线程池。原因有两点:1、AsyncTask 和 Activity 的生命周期没有紧密联系,比如旋转屏幕导致 Activity 被重建,而 AsyncTask 持有的是之前的 Activity 对象,所以它的更新 UI 都是没有作用的。2、使用 AsyncTask 的内存泄漏问题,我们经常把 AsyncTask 作为内部类来方便的引用外部 Activity 的控件来进行更新进度或者结果,这就导致了 Activity 已经不可见了,但是仍被 AsyncTask 引用着,导致资源不能够回收。
分析源码可以让我们正确使用工具,思考一些之前没有考虑过的问题,更可以学习顶级程序员是如何写代码的。所以后续我还会继续分析安卓开发中常用的工具原理。最后,这篇分析也仅是个人的看法,如果哪里有不正确之处,还请各位大佬指出。