- Asynchronous Android Programming(Second Edition)
- Helder Vasconcelos
- 1003字
- 2021-07-14 10:43:16
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.
- Qt 5 and OpenCV 4 Computer Vision Projects
- 企業級Java EE架構設計精深實踐
- CMDB分步構建指南
- 兩周自制腳本語言
- 從程序員到架構師:大數據量、緩存、高并發、微服務、多團隊協同等核心場景實戰
- Building an RPG with Unity 2018
- SQL Server從入門到精通(第3版)
- 單片機C語言程序設計實訓100例
- SQL Server 2008 R2數據庫技術及應用(第3版)
- BeagleBone Robotic Projects(Second Edition)
- C++從入門到精通(第6版)
- Fastdata Processing with Spark
- 深入理解BootLoader
- C語言程序設計實踐
- .NET 4.0面向對象編程漫談:應用篇