65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 AsyncTask 进行异步任务

2013年4月19日

CPOL

5分钟阅读

viewsIcon

100915

downloadIcon

1456

如何在 Android Activities 中处理异步任务。

引言

在 SDK 1.5 之后,Android Activity 中提供了一个用于执行异步任务的 API,无需担心线程以及访问 UI 的方式。为此,必须使用 AsyncTask。此处提供了一个如何正确使用此 API 的简单示例。之后,将讨论何时使用(以及何时不使用)此 API。

背景

为了理解此 API 的重要性,需要认识到在 Android Activity 中执行耗时任务时,UI 不能在任务执行期间被锁定。UI 必须可访问,以免用户认为正在执行的程序已冻结。此外,在执行耗时任务时,可能需要告知用户此任务的进度。因此,为了实现所有这些目标,必须利用 AsyncTask API,它允许在显示进度并保持用户界面可交互的同时执行耗时工作。

Using the Code

要使用 AsyncTask API,必须遵循以下步骤

  1. 创建一个继承自 AsyncTask 的类。
  2. 填写类中作为泛型的通用类型,用于
    1. 任务执行的参数数组
    2. 进度参数数组
    3. 结果参数数组
  3. 实现 `doInBackground(Parameters... parameters)` 方法。此方法必须执行需要大量处理的工作。
  4. 可选地,可以实现以下方法:
    1. 取消任务 - `onCancelled(...)`
    2. 在耗时任务执行之前执行任务 - `onPreExecute(...)`
    3. 报告进度 - `onProgressUpdate(...)`
    4. 在耗时任务完成之后执行操作 - `onPostExecute(...)`。

有鉴于此,现在将给出一个示例,代码展示了上述内容。

下面显示的是包含待执行耗时任务的 Activity 的 XML 布局。此 Activity 包含开始/重新开始按钮和取消按钮,分别用于启动和取消任务。它还包含一个标签,用于显示任务状态,以及一个进度条,用于显示完成百分比。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:id="@+id/txtMessage"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />

<ProgressBar
    android:id="@+id/progressBar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="304dp"
    android:layout_height="wrap_content" />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <Button
        android:id="@+id/btnRestart"
        android:onClick="restartOnclick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Restart" />

    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="cancelOnclick"
        android:text="Cancel" />

</LinearLayout>

</LinearLayout>

给定 Activity 的 XML 布局,下面将展示 Activity 的实现。请注意,代码注释非常详尽,以便于理解。本文档中还提供了完整应用程序的下载。现在,将对代码中最重要部分进行进一步解释。

package com.asynctask;

import java.text.MessageFormat;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

public class MainActivity extends Activity {
    private Button btnRestart;
    private Button btnCancel = null;
    private TextView txtMessage =  null;
    private ProgressBar mProgressBar =  null;
    private HugeWork task = null;
    private static final int MAX_PROGRESS = 10;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        btnRestart = (Button) findViewById(R.id.btnRestart);
        btnCancel = (Button) findViewById(R.id.btnCancel);
        txtMessage = (TextView) findViewById(R.id.txtMessage);
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);

        // set an arbitrary max value for the progress bar
        mProgressBar.setMax(MAX_PROGRESS);
        // start the async task
        start();
    }

    // Cancel the async task and handle buttons enablement. Note that
    // the Cancel button is disabled because the task is finished and the
    // restart button is enabled so one can execute the process again.
    //
    // this is the listener for the Cancel Button.
    public void cancelOnclick(View v) {
        task.cancel(true);
        btnCancel.setEnabled(false);
        btnRestart.setEnabled(true);
    }

    // Restart the process execution. This is the listener to the Restart button.
    public void restartOnclick(View v) {
        start();
    }

    // Here we start the big task. For that, we reset the progress bar, set the
    // cancel button to be enable so one can stop the operation at any time and
    // finally we disable the restart button because the task is on-going.
    private void start() {
        // instantiate a new async task
        task = new HugeWork();
        // start async task setting the progress to zero
        task.execute(0);
        // reset progress
        mProgressBar.setProgress(0);
        // handle buttons
        btnCancel.setEnabled(true);
        btnRestart.setEnabled(false);
    }

    // execute the hard will which will take a lot of time. For our example,
    // 1 second.
    private void executeHardWork() {
        try {
            Thread.sleep(1000);
        }
         catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // This class implements the methods for an async task to be executed
    // The only required method is the doInBackground(Params... params). This
    // method execute the big job in background. The other methods are not
    // required but they are implemented here so you can better understand how
    // tye work.
    //
    // Note that this class has three generic types assigned to Integer. These
    // types represents the arguments of the implemented methods.
    //
    // The first one, is passed when the async task is executed. It is an array
    // of necessary elements to be passed for the async task to be executed, in
    // case there is a need to do so. This parameter is used in the method doInBackground(...).
    //
    // The second parameter is eh type used for progress.
    // Thus, when onProgressUpdate(...) is called,
    // the parameters for this methods are of the type of this second parameter.
    //
    // The third parameter is used for when the task is complete.
    // Note that this parameter is the
    // return type of the method doInBackground(...) and
    // the parameter of the methods onPostExecute(...)
    // and onCancelled(...).
    class HugeWork extends AsyncTask<Integer, Integer, Integer> {

        // Method executed before the async task start. All things needed to be
        // setup before the async task must be done here. In this example we
        // simply display a message.
        @Override
        protected void onPreExecute() {
            txtMessage.setText("Executing async task...");
            super.onPreExecute();
        }

        // Here is where all the hard work is done. We simulate it by executing
        // a sleep for 1 second, 10 times. Each time the sleep is performed, we update
        // our progress in the method publishProgress(...). This method executes the
        // overridden method onProgressUpdate(...) which updates the progress.
        @Override
        protected Integer doInBackground(Integer... params) {

            // get the initial parameters. For us, this is the initial bar progress = 0
            int progress = ((Integer[])params)[0];

            do {

                // only keep going in case the task was not cancelled
                if (!this.isCancelled()) {
                    // execute hard work - sleep
                    executeHardWork();
                }
                else {
                    // in case the task was cancelled, break the loop
                    // and finish this task
                    break;
                }

                // upgrade progress
                progress++;
                publishProgress(progress);
            } while (progress <= MAX_PROGRESS);

            return progress;
        }

        // Every time the progress is informed, we update the progress bar
        @Override
        protected void onProgressUpdate(Integer... values) {
            int progress = ((Integer[])values)[0];
            mProgressBar.setProgress(progress);
            super.onProgressUpdate(values);
        }

        // If the cancellation occurs, set the message informing so
        @Override
        protected void onCancelled(Integer result) {
            txtMessage.setText(MessageFormat.format
            ("Async task has been cancelled at {0} seconds.", result - 1));
            super.onCancelled(result);
        }

        // Method executed after the task is finished. If the task is cancelled this method is not
        // called. Here we display a finishing message and arrange the buttons.
        @Override
        protected void onPostExecute(Integer result) {
            txtMessage.setText(MessageFormat.format
            ("Async task execution finished in {0} seconds.", result - 1));
            btnCancel.setEnabled(false);
            btnRestart.setEnabled(true);
            super.onPostExecute(result);
        }
    }
}

下面显示的方法代表了异步功能的 it 心。此方法执行耗时任务(调用耗时方法 `executeHardWork()`),并使用 `publishProgress(progress)` 方法报告其进度,这将导致 `onProgressUpdate(...)` 事件被执行。请注意,此处还会验证任务是否已被取消,然后再继续执行。

因此,此处实现了对长时间异步任务执行所需的所有功能。任务不会冻结 UI,可以被取消,并且可以报告其进度。

// Here is where all the hard work is done. We simulate it by executing
// a sleep for 1 second, 10 times. Each time the sleep is performed, we update
// our progress in the method publishProgress(...). This method executes the
// overridden method onProgressUpdate(...) which updates the progress.
@Override
protected Integer doInBackground(Integer... params) {

    // get the initial parameters. For us, this is the initial bar progress = 0
    int progress = ((Integer[])params)[0];

    do {

        // only keep going in case the task was not cancelled
        if (!this.isCancelled()) {
            // execute hard work - sleep
            executeHardWork();
        }
        else {
            // in case the task was cancelled, break the loop
            // and finish this task
            break;
        }

        // upgrade progress
        progress++;
        publishProgress(progress);
    } while (progress <= MAX_PROGRESS);

    return progress;
} 

继续,看看下面显示的方法。它通过显示进度来与 UI 进行交互。请注意,进度的百分比确实由 `progress` 变量提供。此外,请注意,此方法 `onProgressUpdate(...)` 正如之前提到的,是由 `publishProgress(progress)` 方法调用的。

// Every time the progress is informed, we update the progress bar
@Override
protected void onProgressUpdate(Integer... values) {
    int progress = ((Integer[])values)[0];
    mProgressBar.setProgress(progress);
    super.onProgressUpdate(values);
} 

另一个值得一提的方法是 `onCancelled(Integer)`。此事件会在执行取消时被调用。此方法仅显示一条消息,表明新的状态(已取消)。请注意,当取消分派时,`doInBackground(...)` 方法会在 `isCancelled() ` 验证返回 `true` 时停止执行。需要强调的是,当发生取消时,`onPostExecute(...)` 方法不会被调用。在代码中,取消是由 Cancel 按钮事件 `cancelOnclick(View)` 触发的。在 AsyncTask 对象上调用的方法是 `AsyncTask.cancel(boolean)`。

 // If the cancellation occurs, set the message informing so
@Override
protected void onCancelled(Integer result) {
    txtMessage.setText(MessageFormat.format
    ("Async task has been cancelled at {0} seconds.", result - 1));
    super.onCancelled(result);
}

最后讨论的方法是 `onPostExecute(Integer)`。它在任务完成后执行必要的后续工作。此处显示一条完成消息并重置表单,以便可以重新开始作业。再次强调,如前所述,当任务被取消时,此方法不会被调用。

 // Method executed after the task is finished. If the task is cancelled this method is not
// called. Here we display a finishing message and arrange the buttons.
@Override
protected void onPostExecute(Integer result) {
    txtMessage.setText(MessageFormat.format
    ("Async task execution finished in {0} seconds.", result - 1));
    btnCancel.setEnabled(false);
    btnRestart.setEnabled(true);
    super.onPostExecute(result);
}

何时使用此 API

阅读完本文后,读者可能会想,是否应该对用户执行的每一项操作都使用这种与 UI 交互的方式。例如,假设您的 UI 显示一个项目列表,并且您可以编辑、删除、更新或更改其中一组项目。对于这些操作中的每一个,您可能会倾向于使用此 API,并认为这样做可以避免用户与冻结的表单进行交互的风险。对此问题的回答是:不,并非每项用户操作都需要使用此 API,原因如下:

  1. 大多数用户操作都会得到快速响应,因此无需担心冻结窗口,只需实现事件即可,这样更简单。
  2. 代码更难维护。想象一下前面描述的情况,即有一个列表,其中可以执行许多操作。代码将变得单调重复,容易出现许多错误,并且大部分编写的代码将变得无用,因为不需要复杂的异步交互。

然而,应了解此 API 对良好的用户体验至关重要的场景。这些情况遵循一个简单的规则,该规则很明显但值得讨论:只要操作花费的时间太长 - 超过一两秒 - 就必须提醒用户作业仍在执行中。以下是这些情况最可能发生的情况:

  1. 活动涉及网络通信。每当应用程序需要与互联网或本地网络交互时,很可能会有延迟。因此,应使用 AsyncTask API。
  2. 应用程序正在启动并且正在进行繁重处理。这种情况主要发生在游戏中。请注意,大多数花哨的游戏打开时需要一段时间才能启动。这是显示进度条、说明情况的标签以及等待消息的理想场景。

结论

本文详细解释了 Android 异步任务 API 的工作原理。此 API 非常有趣,因为生成的代码相当有条理,并且使用线程实现起来可能非常困难的事情变得简单易懂且易于维护。最后,我们还讨论了何时适合使用此 API 以及何时不适合。

© . All rights reserved.