官术网_书友最值得收藏!

Canceling an AsyncTask

Another nice usability touch we can provide for our users is the ability to cancel a task before it completes—for example, if after starting the execution, the user is no longer interested in the operation result. AsyncTask provides support for cancellation with the cancel method.

public final boolean cancel(boolean mayInterruptIfRunning)

The mayInterruptIfRunning parameter allows us to specify whether an AsyncTask thread that is in an interruptible state, may actually be interrupted—for example, if our doInBackground code is performing a blocking interruptible function, such as Object.wait(). When we set the mayInterruptIfRunning as false, the AsyncTask won't interrupt the current interruptible blocking operation and the AsyncTask background processing will only finish once the blocking operation terminates.

Note

In well behaved interruptible blocking functions, such as Thread.sleep(), Thread.join(), or Object.wait(), the execution is stopped immediately when the thread is interrupted with Thread.interrupt() and it throws an InterruptedException. The InterruptedException should be properly handled and swallowed only if you know the background thread is about to exit.

Simply invoking cancel is not sufficient to cause our task to finish early. We need to actively support cancellation by periodically checking the value returned from isCancelled and reacting appropriately in doInBackground.

First, let's set up our ProgressDialog to trigger the AsyncTask's cancel method by adding a few lines to onPreExecute:

@Override
protected void onPreExecute() {
 ...
 progress.setCancelable(true);
 progress.setOnCancelListener(
 new DialogInterface.OnCancelListener() {
 public void onCancel(DialogInterface dialog) {
 DownloadImageTask.this.cancel(false);
 }
 });
 ...
}

Now we can trigger cancel by touching outside the progress dialog, or pressing the device's back button while the dialog is visible.

We'll invoke cancel with false, as we don't want to immediately suspend the current IO operation during a network read or check the return value of the Thread.interrupted() function. We still need to check for the cancellation in doInBackground, so we will modify it as follows:

private Bitmap downloadBitmap(URL url) {
  Bitmap bitmap = null;
  BufferedInputStream bif = new BufferedInputStream(is) {
    ...

    public int read(byte[] buffer, int byteOffset,
                    int byteCount) throws IOException {

      // Read the bytes from the Connection
      int readBytes = super.read(buffer, byteOffset, byteCount);
     
      // Verify if the download was cancelled
      if ( isCancelled() ) {
        // Returning -1 means that there is
        // no more data and the stream has just ended
        return -1;
      }
      ...
    }
  }
  // If the download is cancelled the Bitmap is null
  if ( !isCancelled() ) {
    bitmap = BitmapFactory.decodeStream(bif);
  }
  return bitmap;
  }

In the code above, in our Anonymous subclass of BufferInputStream we are able to intercept each read that happens on the connection. When that is in place, and once we cancel the AsyncTask, we are able to stop the data stream by simple returning a -1(End of stream) as the result of the read invoke. As soon as the BitmapFactory.decodeStream receives the end of the stream, it returns immediately and we return null as the result of the downloadBitmap invoke.

The cancelled AsyncTask does not receive the onPostExecute callback. Instead, we have the opportunity to implement different behavior for a cancelled execution by implementing onCancelled. There are two variants of this callback method:

protected void onCancelled(Result result);
protected void onCancelled();

The default implementation of the parameterized onCancelled(Result result) method delegates to the onCancelled() method after it finishes.

If AsyncTask cannot provide either a partial result (such as a partial image data) or nothing, then we will probably want to override the zero argument onCancelled() method.

On the other hand, if we are performing an incremental computation in syncTask, we might choose to override the onCancelled(Result result) version when the partial result has some meaning to your application.

In both cases, since onPostExecute() does not get called on a canceled AsyncTask, we will want to make sure that our onCancelled() callbacks update the user interface appropriately—in our example, this entails dismissing the progress dialog we opened in onPreExecute(), and updating the image view with a default image available as drawable on the application package.

In our example, when the task is cancelled, the result from doInBackground() is a null object so we will override the no-argument onCancelled() function to add the behavior described previously:

@Override
protected void onCancelled() {
  if ( imageView !=null && imageView.get() != null &&
       ctx !=null && ctx.get() != null ) {
   
   // Load the Bitmap from the application resources
    Bitmap bitmap = BitmapFactory.decodeResource(
                      ctx.get().getResources(),
                      R.drawable.default_photo
                    );
    // Set the image bitmap on the image view
    this.imageView.get().setImageBitmap(bitmap);
  }
  // Remove the dialog from the screen
  progress.dismiss();
}

Another situation to be aware of occurs when we cancel an AsyncTask that has not yet begun its doInBackground() method. If this happens, doInBackground() will never be invoked, though onCancelled() will still be called on the main thread.

AsyncTask Execution State

The execute() method, could finish in a cancelled state or in a completed state, however if the user tries to call execute() a second time, the task will fail and throw an IllegalStateException exception saying:

Cannot execute task, a task can be executed only once/the task is already running

With a reference to an AsyncTask object in hand, we can ascertain the status of your task over the getStatus() method, and react according to the status result. Let's take a look at the next snippet:

// Create a download task object
DownloadImageTask task  = new DownloadImageTask(
                            ShowMyPuppyActivity.this, iv);
...
if ( task.getStatus() == AsyncTask.Status.PENDING ) {
  // DownloadImageTask has not started yet so
  // we can can invoke execute()
} else if (task.getStatus() == AsyncTask.Status.RUNNING) {
  // DownloadImageTask is currently running in
  // doInBackground()
} else if (task.getStatus() == AsyncTask.Status.FINISHED
           && task.isCancelled()) {
  // DownloadImageTask is done OnCancelled was called
} else {
  // DownloadImageTask is done onPostExecute was called
}

Using the getStatus() instance method provided by AsyncTask we can keep up with the execution of the background task and know exactly what the current status of your background work is.

Note

If you want to repeat your background you have to instantiate a new task and call the execute() method again.

主站蜘蛛池模板: 电白县| 寻乌县| 江永县| 当涂县| 余姚市| 黄石市| 宁远县| 铜梁县| 朝阳区| 商丘市| 赣州市| 延寿县| 丹东市| 祁门县| 讷河市| 游戏| 花莲市| 佛教| 肥乡县| 资溪县| 中超| 海口市| 双鸭山市| 昌都县| 潞西市| 武邑县| 平遥县| 宜阳县| 延庆县| 诏安县| 长武县| 柳林县| 新余市| 兴文县| 观塘区| 梁河县| 苗栗市| 绩溪县| 华阴市| 崇礼县| 阿荣旗|