AsyncTask的基本用法
AsyncTask本身是一个抽象类,若想要使用它,需要创建一个子类去继承它,且必须复写它的抽象方法doInBackground()。
在继承AsyncTask类时需要指定三个泛型参数:
public abstract class AsyncTask {
......
}
这三个参数的用途:
- Params
在执行AsyncTask的execute(Params)时传入的参数,可用于在doInBackground()任务中使用。 - Progress
后台执行任务进度值的类型。 - Result
后台任务执行完毕后,如果需要结果返回,可指定为Result类型的泛型数据作为返回值类型。
举个具体的例子:
/**
* Created by Kathy on 17-2-17.
*/
public class MyTask extends AsyncTask
第一个泛型参数指定为Object,表示在执行AsyncTask任务时,execute(Object)方法需要传入Object类型的参数给后台;
第二个泛型参数指定为Integer,表示将使用整型数据作为进度显示单位;
第三个泛型参数指定为Boolean,标识用布尔型数据来反馈执行结果。
AsyncTask的基本方法及用途
onPreExecute()
这个方法在后台任务执行前调用,用于进行界面的初始化,比如显示一个进度条对话框等。doInBackground(Params... params)
必须重写的抽象方法!这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果进行返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI,比如说反馈当前任务的执行进度,可以调用publishProgress(Progress...)方法来完成。publishProgress(Progress... values)
反馈当前任务的执行进度,在doInBackground()中调用。onProgressUpdate(Progress... values)
当在后台任务中调用了publishProgress(Progress...)方法后,这个方法就很快会被调用,方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应更新。onPostExecute(Result result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。boolean cancel(boolean mayInterruptIfRunning)
试图取消任务的执行,但是如果任务已经被完成会取消失败。执行这个方法会导致doInBackground()执行完后不会去执行onPostExecute()方法,而是执行onCancelled()方法。boolean isCancelled()
如果在任务执行完成前被取消,该方法返回true。onCancelled(Result result)
任务被取消后回调的方法。
简单实例
class DownloadImageTask extends AsyncTask {
@Override
protected void onPreExecute() {
// 显示进度条
progressBar.show();
}
@Override
protected Boolean doInBackground(Void... params) {
int downloadprogress = doDownload();
// 通知进度条更新UI
publishProgress(progress);
return true;
}
@Override
protected void onProgressUpdate(Integer... values) { 、
// 更新进度条
progressBar.setProgress(values[0] );
}
@Override
protected void onPostExecute(Boolean result) {
// 进度条消失
progressBar.setVisibility(View.INVISIBLE);
progressBar.setProgress(0);
}
}
在UI线程里执行上述任务,只需要调用如下execute()方法即可:
new DownloadImageTask().execute();
源码角度分析AsynTask
启动AsynTask任务,需要构建它的实例,首先来看AsyncTask的构造函数:
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
mWorker = new WorkerRunnable() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
e
mFuture = new FutureTask(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的构造函数只是初始化了两个变量mWorker和mFuture,并在初始化mFuture时传入了mWorker作为参数。
如果需要启动某个任务,需要调用AsyncTask的execute()方法:
@MainThread
public final AsyncTask execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
//向下调用
@MainThread
public final AsyncTask 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()方法
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
这里判断了任务的状态,任务有三种状态:PENDING / RUNNING / FINISHED
/**
* Indicates the current status of the task. Each status will be set only once
* during the lifetime of a task.
*/
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING,
/**
* Indicates that the task is running.
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED,
}
可以发现execute()执行方法中,最先被调用的是onPreExecute()方法。接下来发现调用了exec.execute(mFuture)这个语句并将mFuture这个FutureTask对象传递进来,那么exec是Executor类型的,且向上追溯,是执行execute()方法时由sDefaultExecutor传递进来的,且看sDefaultExecutor定义的地方:
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
可以看到sDefaultExecutor的赋值是一个常量,且为SerialExecutor类的实例,则exec.execute(mFuture)的执行方法可以追溯到SerialExecutor的execute()方法中。
private static class SerialExecutor implements Executor {
final ArrayDeque mTasks = new ArrayDeque();
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()方法,这个方法里的所有逻辑就是在子线程中执行的。且执行execute()方法时传入的Runnable对象为FutureTask对象,所以会调用到FutureTask类的run()方法。最终的最终,会执行到mWorker对象的call()方法:
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
可以看到以上方法中调用到了doInBackground(mParams)去做耗时处理,所以doInBackground()实在onPreExcute()方法之后执行,且在子线程中执行。
接下来,将返回值类型为Result的doInBackground()的执行结果传递到postResult(result)中:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult(this, result));
message.sendToTarget();
return result;
}
postResult()方法的作用是什么呢?它使用Handler发送一条消息MESSAGE_POST_RESULT,并携带上AsyncTaskResult对象,这个对象中携带任务执行的结果。那么接收这条消息的回调方法在哪儿呢?这个取决于发送消息的Handler:
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
InternalHandler的回调方法如下,且发现InternalHandler的Looper是主线程的,所以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:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
从以上InternalHandler可以看到:
1.如果收到MESSAGE_POST_RESULT消息,会执行finish()方法;
2.如果收到MESSAGE_POST_PROGRESS消息,会执行onProgressUpdate()方法。
我们来看finish()方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
逻辑很清晰了,执行完doInbackground()方法后,返回的结果通过IntenalHandler的实例发送消息传递给IntenalHandler的回调方法,最后,在主线程中执行finish()方法;如果任务执行被取消,则执行onCancelled()方法;如果没有被取消,则继续向下执行onPostExecute()方法,且将状态设置为Status.FINISHED。
可以看到,在IntenalHandler的回调方法中接收另外一个消息MESSAGE_POST_PROGRESS,这个消息是在哪儿发送的呢?
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult
以上可知,在doInBackground()方法中通过调用publishProgress()实现子线程 和UI线程通信,publishProgress()通过handler发送消息,在主线程中接收消息的地方会执行到onProgressUpdate()方法去更新UI。
综上可知,Asynctask背后使用的仍然是Handler异步消息处理机制,只是在源码层做了封装,我们在使用时,不必考虑而已。
使用AsyncTask的注意事项
- AsynTask的实例需要在UI线程中创建
- execute(Params... params)方法必须在UI线程中调用
- AsynTask的doInBackground(Prams)方法执行异步任务运行在子线程中,其他方法运行在主线程中,可以操作UI组件。
- 运行后,可以随时调用AsynTask对象的cancel(boolean)方法取消任务,如果成功,调用isCancelled()将返回true,并且不再执行onPostExcute()方法,而是执行onCancelled() 方法。
你必须知道的AsyncTask的缺陷
1.生命周期
AsyncTask不随着Activity的生命周期的销毁而销毁,这使得AsyncTask执行完成后,Activity可能已经不在了。AsyncTask会一直执行doInBackground()方法直到执行结束。一旦上述方法结束,会依据情况进行不同的操作。
但是,AsyncTask的cancel(mayInterruptIfRunning)可以通过传入一个布尔值来打断执行的任务,mayInterruptIfRunning设置为true,表示打断任务,正在执行的任务会继续执行直到完成。但是可以在doInBackground()方法中有一个循环操作,通过调用isCancelled()来判断任务是否被打断,如果isCancelled() == true,则应避免再执行后续的一些操作。
总之,使用AsyncTask需要确保AsyncTask正确地取消。
2.内存泄露风险
在Activity中使用非静态匿名内部AsyncTask类,由于Java内部类的特点,AsyncTask内部类会持有外部类的隐式引用。由于AsyncTask的生命周期可能比Activity的长,当Activity进行销毁AsyncTask还在执行时,由于AsyncTask持有Activity的引用,导致Activity对象无法回收,进而产生内存泄露。
3.结果丢失
另一个问题就是在屏幕旋转等造成Activity重新创建时AsyncTask数据丢失的问题。当Activity销毁并重新创建后,还在运行的AsyncTask会持有一个Activity的非法引用即之前的Activity实例。
4.串行还是并行?
new AsyncTask1.execute();
new AsyncTask2.execute();
上面两个任务是同时执行呢,还是AsyncTask1执行结束之后,AsyncTask2才能执行呢?实际上是结果依据API不同而不同。
关于AsyncTask时串行还是并行有很多疑问,这很正常,因为它经过多次的修改。
在1.6(Donut)之前:
在第一版的AsyncTask,任务是串行调度。一个任务执行完成另一个才能执行。由于串行执行任务,使用多个AsyncTask可能会带来有些问题。所以这并不是一个很好的处理异步(尤其是需要将结果作用于UI试图)操作的方法。
从1.6到2.3(Gingerbread)
后来Android团队决定让AsyncTask并行来解决1.6之前引起的问题,这个问题是解决了,新的问题又出现了。很多开发者实际上依赖于顺序执行的行为。于是很多并发的问题蜂拥而至。
3.0(Honeycomb)到现在
好吧,开发者可能并不喜欢让AsyncTask并行,于是Android团队又把AsyncTask改成了串行。当然这一次的修改并没有完全禁止AsyncTask并行。你可以通过设置executeOnExecutor(Executor)来实现多个AsyncTask并行。关于API文档的描述如下
If we want to make sure we have control over the execution, whether it will run serially or parallel, we can check at runtime with this code to make sure it runs parallel