AsyncTask源码分析
概述
AsyncTask是安卓API里提供的一个多线程操作工具类,能够灵活方便地从子线程切换到UI线程。相信包括我在内的很多开发者在平常的项目种都有用到这个多线程工具,笔者在使用过程中就遇到一些问题,所以接下来会简单的描述其使用,然后分析其源码逻辑,解决自己遇到的疑问。
基本使用
一般使用AsyncTas如下:
private class MyAdAysncTask extends AsyncTask<Uri, Integer, Boolean> {
// 构造方法传参
private MyAdAysncTask(Object Params) {
}
@Override
protected Boolean doInBackground(Uri... uris) {
try {
while (true) {
int downloadPercent = Downloader.downloadFile(uri);
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Integer... values) {
//显示下载进度
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean adBean) {
super.onPostExecute(adBean);
}
}
// 执行任务
new MyAdAysncTask("传入需要的参数").execute();
查看AsyncTask源码可以知道AsyncTask是一个抽象类,当我们要使用它的时候,需要创建一个子类去继承它,然后实现相应的方法。从上面的实现可以看到,AsyncTask有三个泛型参数如下:
- Params:该参数为AsyncTask执行时要传入的参数,可用于后台任务执行时使用;
- Progress:该参数为子线程任务执行时发布的当前进度参数,用于进度显示;
- Result:该参数为子线程任务执行完时返回的结果类型。
跟进上面的参数,下面介绍AsyncTask中主要的方法。
- onPreExecute()
该方法在子线程任务执行前调用,一般做一些界面初始化操作。 - doInBackground(Uri… uris)
该方法为子线程任务方法,运行在子线程中,用以处理耗时操作,可以接收Params参数,在任务完成后可以返回结果,如果返回参数为Void则不返回,另外在该方法执行时,可以调用publishProgress(Integer… values)方法来发布当前处理进度,以便ui显示。 - onProgressUpdate(Integer… values)
顾名思义就是用来更新进度的方法,在doInBackground方法中如果调用publishProgress方法则可以在这个方法内进行进度显示。 - onPostExecute(Boolean adBean)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用,主要是做一个任务的收尾工作。
有了AsyncTask后,在子线程与UI线程之间的切换我们就不用在大费周章的使用Handler与Thread,只需简单的调用即可实现。
源码分析
为了更好的理解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);
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);
}
}
};
}
在构造函数中主要创建了两个对象,工作线程对象与任务调度栈,可以看到工作线程中调用了doInBackground()方法,因此doInBackground()在子线程执行,但当execute未执行时doInBackground()是还没开始调用的,关于它们的作用后续会介绍。
当我们使用AsyncTask时会一般使用execute方法来执行,下面我们来看看它的执行流程:
@MainThread
// 主线程执行
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
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;
}
从源码可以看到execute()方法至在主线程执行的,在执行execute()方法后会首先调用onPreExecute方法,所以可以在这个方法里做一些界面初始化的工作。在这个方法执行之后,执行了关键代码exec.execute(mFuture),从字面上看,是通过Executor对象实例执行了一个任务,通过上面的代码可以看到,这个Executor对象是sDefaultExecutor,它的创建如下:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
// 创建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);
}
}
}
很明显,当exec.execute(mFuture)执行了,其实就是把mFuture抛给了SERIAL_EXECUTOR来执行,然后调用mFuture中的run()方法。而在上面也介绍过,mFuture是在AsyncTask构造方法中被创建的FutureTask对象,下面我直接看下FutureTask中的run()方法:
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
// 这里的call其实是初始化时创建的WorkerRunnable对象
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 调用WorkerRunnable的call方法
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
根据上面源码描述,FutureTask中的run()方法会调用到WorkerRunnable对象的call()方法,而在上面的构造函数说明时已经阐述过WorkerRunnable对象创建过程,下面来重新看下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;
}
};
注:此时线程已经切换到子线程
到这里,我们终于看到了doInBackground()的调用,这也可以证明了该方法是在子线程运行的,因此我们可以做一些耗时操作。在这个方法执行完后会返回AsyncTask创建时的第三个参数对象,然后执行postResult(result),我们继续来看下postResult(result)的实现:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
// 将result投递到handler所在的线程
message.sendToTarget();
return result;
}
在postResult()的主要工作就是把结果投递到getHandler()获取到的Handler对象所在的线程,按AsyncTask的工作原理,这个线程肯定是主线程,下面来看下这个Handler对象获取过程:
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
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;
}
}
}
private void finish(Result result) {
// 任务是否取消
if (isCancelled()) {
onCancelled(result);
} else {
//执行最后一步
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
到这里,一个AsyncTask的任务执行结束过程就很明显了,Handler内部对消息的类型进行了判断,如果这是一条MESSAGE_POST_RESULT消息,就会去执行finish()方法,如果这是一条MESSAGE_POST_PROGRESS消息,就会去执行onProgressUpdate()方法。而正常情况下都是执行finish()方法,而在这个方法中可以看到,如果当前任务被取消掉了,就会调用onCancelled()方法,如果没有被取消,则调用onPostExecute()方法,这样当前任务的执行就全部结束了。
而在上面描述doInBackground()方法时介绍了,可以调用publishProgress()方法来发布任务处理进度,下面来看看这个方法实现:
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
//发布ui进度更新消息
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
可以看到,publishProgress()也是通过handler对象来发送ui更新消息,从而完成对UI元素的更新操作。
至此,AsyncTask的源码介绍已经结束,从整个流程来看,AsyncTask只是谷歌利用Thread与Handler机制封装了一个方便使用者调用的工具类,但它的实现模式确实是我们可以好好学习的。
遇到的坑
使用AsyncTask进行异步任务时,我们会经常在生命周期结束的时候尝试调用AsyncTask的cancel()方法来取消任务,但有些时候你会遇到代码已经执行了cancel()方法但最终AsyncTask还是调用了其他回调方法,而生命周期回调的方法已经回收了一些资源,导致主线代码异常,为此我看了下cancel()方法实现以及介绍:
/**
* <p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
*
* <p>Calling this method will result in {@link #onCancelled(Object)} being
* invoked on the UI thread after {@link #doInBackground(Object[])}
* returns. Calling this method guarantees that {@link #onPostExecute(Object)}
* is never invoked. After invoking this method, you should check the
* value returned by {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])} to finish the task as early as
* possible.</p>
*
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete.
*
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*
* @see #isCancelled()
* @see #onCancelled(Object)
*/
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
从上面的注释可以看出,这个取消操作可能会因为任务已经执行完,已经取消或者由于一些其他原因不能被取消导致任务继续进行,所以大家在使用时也不要太信任源码提供的方法。
就此对于AsyncTask源码分析就此结束,由于笔者技术有限,如果分析过程中有出现错误,欢迎指出,谢谢。