[译] 使用AsyncTask的光明和黑暗面之一

 

导读:

在Android上进行异步任务最重要的作用是将UI线程从长时间运行的操作中解放出来。这往往需要我们单独定义一个后台线程来执行需要长时间操作的任务,最后让UI线程和后台线程进行通信。

以上所有我们需要的元素都封装在AsyncTask中,它使异步执行变得非常简单。

接下来我们将探讨AsyncTask类的细节,并展示它是如何平滑地处理后台任务的执行,但也要小心这里面的陷阱。

基本用法

顾名思义,AsyncTask是在后台线程上执行的异步任务。在类中需要重写的惟一方法是doInBackground()。因此,AsyncTask的最简单的实现是这样的:

public class MinimalTask extends AsyncTask{
    @Override
    protected Object doInBackground(Object... objects){
        //Implement task to execute on background thread.
    }
}

通过调用execute()方法来运行AsyncTask,该方法将会在后台线程中触发doInBackGround()方法的回调:

new MinimalTask().execute(Object... objects);

当AsyncTask完成执行后,就不能再次执行它,因为execute()是个一次性操作,每个AsyncTask实例只能调用一次,这与线程的行为相同。

除了后台执行之外,AsyncTask还提供了一个从execute()到doInBackground()的数据传递机制。任何类型的对象都可以从初始线程传递到后台线程。这类似于HandlerThread,但是使用AsyncTask,您不必关心使用Handler实例发送和处理消息。

注意:从execute中传递给doInBackground的数据由两个线程共享,需要以互斥的方式访问。换句话说,需要同步来保护数据不受损坏。

您希望在后台执行任务并将结果返回给UI线程,AsnycTask正是为此而生;在执行耗时任务之前初始化UI、执行任务、报告任务进度、以及返回执行结果,这一系列流程都可以作为AsyncTask子类的可选回调:

public class FullTask extends AsyncTask<Params, Progress, Result> {
    @Override
    protected void onPreExecute() { ... }
    
    @Override
    protected Result doInBackground(Params... params) { ... }

    @Override
    protected void onProgressUpdate(Progress... progress) { ... }

    @Override
    protected void onPostExecute(Result result) { ... }
    
    @Override
    protected void onCancelled(Result result) { ... }
}

该实现继承自AsyncTask,并定义了在线程间传递的数据:

Params-

        在后台线程中执行任务所需要的参数

Progress-

        后台线程报告的进度数据。将从doInBackgourd()传递至UI线程中的onProgressUpdate()

Result-

       从后台线程生成并发送到UI线程的结果。

 

除了onProgressUpdate(它是由doInBackground发起并与doInBackground并发运行的),所有的回调方法都是顺序执行的。

下图显示了AsyncTask的生命周期及其回调序列。

根据图中的步骤:

  1. 创建AsyncTask实例。
  2.  开始执行任务。
  3.  UI线程上的第一个回调:onPreExecute。这里通常为长时间的操作准备UI—例如,在屏幕上显示进度指示。
  4.  后台线程上的回调:doInBackground。这将执行长时间运行的任务。
  5.  在后台线程上报告来自publishProgress方法的进度更新。这将触发UI线程上的onProgressUpdate回调,它通常通过更改屏幕上的进度指示器来处理更新。进度由Progress参数定义。
  6.  后台执行完成,然后在UI线程上运行回调以返回结果。有两个可能的回调:在默认情况下调用onPostExecute,但是如果AsyncTask被取消,将会调用oncancel()。只可能会发生一个回调。

进度更新机制解决了两个问题:

  • 通过连续报告执行了多少总任务,向用户显示长时间运行的操作的进展情况
  • 以部分的形式交付结果,而不是在onPostExecute的最后交付所有内容。例如,如果任务通过网络下载多个图像,AsyncTask不必等待并在所有图像都下载完时将所有图像交付给UI线程;它可以利用publishProgress每次向UI线程发送一个图像。通过这种方式,用户可以不断地更新UI。

 

创建和启动

AsyncTask实现是使用默认构造函数创建的,应该从UI线程调用该构造函数。构造函数不提供配置参数。相反,参数被传递到任务的开始。

execute(Object... objects)

参数由一个可变大小的对象列表组成,该列表可以接收任意数量的任意类型的对象。在doInBackground回调中检索输入参数。

通过这种方式,数据在UI线程和后台线程之间共享,并且始终对两者可用。

需要注意的是,一个AsyncTask对象被创建出来后,就只能执行一个异步任务,多次调用execute()会生成一个IllegalStateException错误。

取消

如果UI线程决定不使用AsyncTask的结果(可能是因为用户改变了主意或将应用程序放在后台),它可以通过调用cancel(boolean)发送终止请求:

// Start the task
AsyncTask task = new MyAsyncTask().execute(/* Omitted */);
// Cancel the task
task.cancel(true);

如果调用的参数为false,则调用仅设置一个标志,后台线程可以通过isCancelled()检查该标志。如果调用的参数为true,则后台线程还会发送一个interrupt(中断请求) ,后台线程可以捕获InterruptedException或检查Thread.isInterrupted()标志,但需要注意的是interrupt()方法不会真正的中断一个正在运行的线程,而是发出中断请求。然后由线程在一个合适的时刻中断自己。

与后台任务一样,如果无法再使用后台执行的结果,则最好尽早终止。终止会释放分配的资源,并降低内存泄漏的风险。就像线程一样,不能强制终止AsyncTask,而是需要取消策略才能正常结束执行。

当它收到取消请求时,任务将跳过对onPostExecute的调用,而是调用取消回调之一onCancelled()或onCancelled(Result)。
您可以使用cancel回调以与异步任务成功完成时不同的结果或不同的消息向用户更新UI。被取消的任务不一定比没有取消的任务提前完成,因为取消只能确保在后台任务执行之后调用onCancelled回调。

取消策略涉及两个部分:当阻塞方法引发InterruptedException时完成doInBackground,并在代码中使用检查点以查看是否在开始任何长时间操作之前取消了任务。可以在代码中的任何位置插入检查点,但是将检查点添加到任何地方都变得不切实际,因此最好将它们用作循环或两次长操作之间的条件。可以通过检查AsyncTask.isCancelled或捕获interrupt来确定检查点条件。但是,两个检查点条件对cancel的响应不同,如下表所示。

最有效的检查条件是使用isCancelled,因为它观察到调用是否被实际的取消而不是发送中断请求。因此,最靠谱的取消策略是将二者组合如下:

public class InterruptionTask extends AsyncTask<String, Void, Void> {
    
    @Override
    protected Void doInBackground(String... s) {
        try {
            while (!isCancelled()) {
                doLongInterruptibleOperation(s[0]);
            }
        } catch (InterruptedException iex) {
        
        // Do nothing. Let's just finish.
        }
        return null;
    }
}

 

状态

AsyncTask具有以下可能的状态:PENDINGRUNNINGFINISHED

PENDING:

         AsyncTask实例已创建,但尚未在其上调用execute。

RUNNING

执行已经开始; 即execute()被调用。 只要任务的最终方法(例如onPostExecute)仍在运行,任务完成后便会保持此状态。

FINISHED

后台执行和可选的最终操作(onPostExecute或onCancelled)均已完成。

一个AsyncTask实例总是按照上述顺序执行,并且一旦任务处于RUNNING状态,就不可能开始任何新的执行。 FINISH是终端状态; 必须为每次执行创建一个新的AsyncTask实例。
可以使用AsyncTask.getStatus()观察AsyncTask的状态,这对于确定当前是否正在执行任务很有用,如以下示例所示。

Example:一次只执行一个AsyncTask

如果不允许在另一个任务执行时执行AsyncTask,则可以存储对该任务的引用,并在允许执行新任务之前检查旧任务的状态。在AsyncTaskStatusActivity中,AsyncTask是从onExecute方法开始的,这个方法可以在UI线程的任何地方调用,例如,由按钮点击所触发的onClick()方法:

public class AsyncTaskStatusActivity extends Activity {
    private AsyncTask mMyAsyncTask;
    // Activity lifecycle code omitted.
    
    public void onExecute(View v) {
        if (mMyAsyncTask != null && mMyAsyncTask.getStatus() !=
            AsyncTask.Status.RUNNING) {
            mMyAsyncTask = new MyAsyncTask().execute();
        }
    }

    private static class MyAsyncTask extends AsyncTask<String, Void, Void> {
        @Override
        protected Void doInBackground(String... s) {
        // Details omitted.
        return null;
        }
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值