Android/AsyncTask: Do you still have to check "isCancelled" (API 24)? - android

My app uses an AsyncTask to download files while displaying a ProgressDialog (I'm aware that it's deprecated) with a "Cancel" button.
According to this you should check isCancelled() in doInBackground periodically because mytask.cancel(true) won't interrupt doInBackground on its own.
I simply cancelled the task without checking at first and noticed that it still stops doInBackground: Depending on how long I let it download before pressing the "Cancel" button, I've seen different sizes in the resulting file - from just a few kb to a couple of mb - the final size would have been around 9mb.
How is this possible? Do you actually not have to call isCancelled() anymore?
My AsyncTask:
private class DownloadTask extends AsyncTask<String, String, String> {
protected void onPreExecute() {
progressdialog.setMessage("Preparing Download...");
progressdialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressdialog.setProgressNumberFormat(null);
progressdialog.setProgressPercentFormat(null);
progressdialog.setIndeterminate(true);
progressdialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
progressdialog.dismiss();
mytask.cancel(true);
}
});
progressdialog.show();
}
protected String doInBackground(String... bla) {
String error = download();
return error;
}
protected void onProgressUpdate(String... s) {
//....
}
protected void onPostExecute(String s) {
progressdialog.dismiss();
//....
}

According to this you should check isCancelled() in doInBackground
periodically because mytask.cancel(true) won't interrupt
doInBackground on its own.
Actually it is not true.
According to documentation:
After invoking this method, you should check the value returned by
isCancelled() periodically from doInBackground(Object[]) to finish the
task as early as possible.
It means you can additionally check for isCancelled() to stop AsyncTask earlier if it is started.
mytask.cancel(true) will stop execution anyway.
Let`s see under the hood what is going on
When you call mytask.cancel(true):
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
Where mFuture is FutureTask that holds runnable inside
Then mFuture.cancel is called:
public boolean cancel(boolean mayInterruptIfRunning) {
if (state != NEW)
return false;
if (mayInterruptIfRunning) {
if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
return false;
Thread t = runner;
if (t != null)
t.interrupt();
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); // final state
}
else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
return false;
finishCompletion();
return true;
}
Where runner is just
private volatile Thread runner;
Since its just thread, lets see what interrupt does in your case:
If this thread is blocked in an I/O operation upon an interruptible
channel then the channel will be closed, the thread's interrupt status
will be set, and the thread will receive a ClosedByInterruptException.
So if your download() method uses InterruptibleChannel interrupt will work.
In other words looks like you have never had to call isCancelled() to interrupt AsyncTask =) since Thread.interrupt can stop io blocking operation in your case.

Related

Stopping and wrapping 2 sub AsyncTask in android

I need to call task() function from doInBackground() of AsyncTask class. The task function contains 2 sub-async task. So the task() return immediately from doInBackground().
Is it possible to stop(or mark this task done) this task from anywhere else?
How to wrap two sub async task in one.
You don't need to call another AsyncTask from within doInBackground. As a matter of fact, once you get to a high enough API, you'll get an exception. You can call another long running method from within AsyncTask without needing to worry about a thread; you're already in a background thread. If you really feed the need, call another service, but there is no reason to do this.
To stop an AsyncTask, just override OnCancelled. Then you can just call:
task.cancel(true).
EDIT:
If you want to wait for another process to finish before you move on, you can wait for that process to finish by setting a global variable in your class or application and then doing a Thread sleep until done. You will not get an ANR because you are already in a background thread and not on the Main UI:
private boolean processIsDone = false.
//then in your method you are calling from AsyncTask:
private void myLongRunningMethod() {
//do your work here....
//at the end set
processIsDone = true;
}
//in your AsyncTask:
protected Void doInBackground(Void... params) {
//do first part of AsyncTask here
myLongRunningMethod();
do {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (!processIsDone);
//finish the process here.
return null;
}
I do not understand the question exactly but maybe this can help. Use this class in your Activity like this:
myTask = new BackgroundAsyncTask().execute();
And cancel this way:
myTask.cancel(true);
This is the code of the class:
private class BackgroundAsyncTask extends AsyncTask<Object , Object ,String> {
protected void onPreExecute(){
// Do Before execute the main task
}
protected String doInBackground(Object... param) {
//Execute the main task and return for example and String
return res;
}
protected void onPostExecute(String result) {
// You can use the String returned by the method doInBackground and process it
}
}
Hope it's help
About your first question, you can catch event when your task is cancelled by onCancelled() method. try like this:
private CancelTask extends AsyncTask {
private boolean cancelled = false;
protected void onCancelled() {
cancelled = true;
}
protected Object doInBackground(Object... obj) {
do {
// do something...
}while(!cancelled)
}
}
and call AsyncTask.cancel(true); when you want to stop.
CancelTask task = new CancelTask();
task.execute();
...
task.cancel(true);
And about second question, I want to know how you handle two "Sub-AsyncTask"s.
I will try to find a solution after you update your code.

Do something if AsyncTask is cancelled?

I'm using AsyncTask to populate SQLite database. I'm downloading data from a certain webpage and putting it in SQLite tables. The thing is, I want to either download 100% of the data or none. So in case the AsyncTask is for some reason interrupted, I want to delete all the data that has been downloaded so far.
This is how I tried to do it:
#Override
protected void onCancelled() {
super.onCancelled();
dbHandler.deleteFromDatabase(razred);
Log.i("TAG", "AsyncTask cancelled");
}
I thought that "onCancelled" will execute if AsyncTask is interrupted in any way but it doesn't. What could I do to erase data that was made with AsyncTask in case it is cancelled in any way? (ex. activity paused, activity destroyed, internet connection interrupted etc.)
You're on the right track, but in your doInBackground() you also need to specifically call isCancelled() to check if it's cancelled and then return from doInBackground(). Then your code will work properly.
Refer to the AsyncTask documentation for "Cancelling a task"
Here's the quote from the documentation for easy reference:
A task can be cancelled at any time by invoking cancel(boolean). Invoking this method will cause subsequent calls to isCancelled() to return true. After invoking this method, onCancelled(Object), instead of onPostExecute(Object) will be invoked after doInBackground(Object[]) returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of isCancelled() periodically from doInBackground(Object[]), if possible (inside a loop for instance.)
EDIT: Per request, some sample code:
private class MyAsyncTask extends AsyncTask<Void,Void,Void> {
private SQLiteDatabase db;
#Override
protected void onPreExecute() {
// any kind of initialization or setup needed before the
// background thread kicks off. remember: this is still on
// on the main (UI) thread
// since youre doing DB I/O, Ill make believe Im initializing the DB here
db = DatabaseHelper.getInstance(MainActvity.this).getWritableDatabase();
}
/*
* The background thread to do your disk and network I/O. If you need
* to pass in any parameters, this is the first Void in the template
*/
#Override
protected Void doInBackground(Void... params) {
// other stuff you need to do in the background. Since you want an
// all-or-nothing type thing, we will use a transaction to manually
// control the db
db.beginTransaction();
try {
// do network I/O to retrieve what you need and then write to DB.
...
... // if theres a loop in here somewhere when reading the data, check !isCancelled() as part of the condition or as one of the first statements and then break
...
db.setTransactionSuccessful(); // assuming everything works, need to set
// this successful here at the end of the try
} catch (InterruptedException ie) { // or some other exception
cancel(true); // heres where you can call cancel() if youve been interrupted
} catch (IOException ioe) { // if your network connection has problems
cancel(true);
} finally {
db.endTransaction();
// other cleanup, like closing the HTTP connection...
// no need to close the DB if you implement it properly
}
return null; // if there was some return value, that would go here
}
#Override
protected void onCancelled(Void result) {
// depending on how you implement doInBackground(), you may not even need this,
// unless you have a lot of other "state" you need to reset aside from the DB transaction
}
#Override
protected void onPostExecute(Void result) {
// any other items to do on main (UI) thread after doInBackground() finishes
// remember, this only gets called if cancel() is not called!
}
}
Hope that helps!
I know this is not exactly what you've asked for, but I have to say you are doing it all wrong by using the AsyncTask.
There are many cases where your async task will be terminated without you being able to do anything. For such critical tasks as this one, use a Service.
With a Service you can till the system to restart your service in case it is terminated prematurely. You then can continue what you started, or start all over again (deleting all previous downloads...etc).
With an AsyncTask, if the system decided to terminate your async task prematurely, you are not notified nor the AsyncTask is restarted. It just dies in complete silence.
I think in the onpostexecute you could handle anything you wanted to.
private class ParseDownload extends AsyncTask<Summary, Void, Boolean> {
#Override
protected Boolean doInBackground(Summary... urls) {
for (Summary url : urls) {
url.dosomething();
if (isCanceled();) { return false;}
}
return true;
}
#Override
protected void onPostExecute(Boolean result) {
if (!result) {
// delete * from yourtable here...
// and mark the download incomplete etc.
}
}
}
Good Luck

How to return value from async task in android

I created an async task to call my server to get data from DB.
I need to process the result returned from http server call.
From my activity i calling the async task in many places. so i cant use member variable to access the result. is there any way to do?
public Result CallServer(String params)
{
try
{
new MainAynscTask().execute(params);
}
catch(Exception ex)
{
ex.printStackTrace();
}
return aResultM;//Need to get back the result
}
private class MainAynscTask extends AsyncTask<String, Void, Result> {
#Override
protected Result doInBackground(String... ParamsP) {
//calling server codes
return aResultL;
}
#Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
//how i will pass this result where i called this task?
}
Try to call the get() method of AsyncTask after you call the execute() method. This works for me
http://developer.android.com/reference/android/os/AsyncTask.html#get()
public Result CallServer(String params)
{
try
{
MainAynscTask task = new MainAynscTask();
task.execute(params);
Result aResultM = task.get(); //Add this
}
catch(Exception ex)
{
ex.printStackTrace();
}
return aResultM;//Need to get back the result
}
...
...
There are two ways i can suggest -
onPostExecute(Result) in AsyncTask. See http://developer.android.com/reference/android/os/AsyncTask.html#onPostExecute(Result)
Send a broadcast with the result as an extra.
AsyncTask is an asynchronous task so it does NOT make sense to return the result to the caller. Rather handle the result in onPostExecute() like setting the value to TextView etc. Or send a broadcast so that some other listener can handle the result.
Here's how I got around this:
1) Create an interface class that defines a signature for a method to execute on completion:
public interface AsyncIfc {
public void onComplete();
}
2) Set a property on your AsyncTask class to hold the delegate method:
public AsyncIfc completionCode;
3) Trigger the delegate from onPostExecute() in the AsyncTask:
completionCode.onComplete();
4) From your calling logic, set the delegate property to an anonymous method:
task.completionCode = new AsyncIfc() {
#Override
public void onComplete() {
// Any logic you want to happen after execution
}
};
When an asynchronous task is executed, the task goes through 4 steps:
onPreExecute(), invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
doInBackground(Params...), invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.
onProgressUpdate(Progress...), invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
Use a handler
In your activity
Handler mHandler = new Handler() {
#Override public void handleMessage(Message msg) {
String s=(String)msg.obj;
tv.setText(s);
}
};
//result is soap object in this case.
protected void onPostExecute(SoapObject result) {
pd.dismiss();
if(result != null) {
Message msg=new Message();
msg.obj=result.getProperty(0).toString();
mHandler.sendMessage(msg);
}

Android Cancel long operation (Asynctask,thread...?)

I tried AsyncTask, Thread and Handler but i don't get it.
The method readXML() takes about 1-2 minutes and i only need a way to cancel this operation. All the solutions I've found were for short time operations (set flag, check flag and break).
Edit
protected class InitTask extends AsyncTask<Context, Integer, String> {
#Override
protected String doInBackground( Context... params ){
try{
preparing = true;
readXML();
preparing = false;
} catch( Exception e ){
Log.i("test", e.getMessage() );
}
return "COMPLETE!";
}
#Override
protected void onCancelled(){
super.onCancelled();
}
}
// ....
_initTask = new InitTask();
_initTask.execute(this);
// ....
_initTask.cancel(true);
Your problem is that onCancelled is only invoked after doInBackground returns. So you need to check for isCancelled from within your readXML operation. See extract from docs (from http://developer.android.com/reference/android/os/AsyncTask.html) below...
A task can be cancelled at any time by invoking cancel(boolean). Invoking this method will cause
subsequent calls to isCancelled() to
return true. After invoking this
method, onCancelled(Object), instead
of onPostExecute(Object) will be
invoked after doInBackground(Object[])
returns. To ensure that a task is
cancelled as quickly as possible, you
should always check the return value
of isCancelled() periodically from
doInBackground(Object[]), if possible
(inside a loop for instance.)

AsyncTask doInBackground does not run [duplicate]

This question already has answers here:
Android SDK AsyncTask doInBackground not running (subclass)
(9 answers)
Closed 9 years ago.
I'm having a problem with the AsyncTask class. It seems like my task stops working after creating 4 or 5 tasks.
Im having 2 activities. MainActivity which only holds a button that starts a second activity called ImageActivity.
ImageActivity is very simple. it got an onCreate that sets the layout, and then it starts a new AsyncTask that loads an image from the internet. This works fine the first few times. But than it suddenly stops working. The onPreExecute method is run every time, but not the doInBackground method. I have tried to simplify the doInBackground with a sleeping loop, and the same thing happens. I cant understand this behavour since the asynctask is both canceled and set to null in the onDestroy method. So every time i start a new ImageActivity, i also create a fresh AsyncTask.
I recreate the ImageActivity and the task by hitting the back button, and than clicking the button on the MainActivity.
Any ideas anyone? I'm really struggling with this one.
UPDATE: Code that starts the ImageActivity (inside a button onClickListener)
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
intent.setClassName(this, ImageActivity.class.getName());
startActivity(intent);
The code above starts this activity
public class ImageActivity extends Activity {
private AsyncTask<Void, Void, Void> task;
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.main);
task = new AsyncTask<Void, Void, Void>() {
#Override
protected void onPreExecute()
{
Log.d(TAG, "onPreExecute()");
}
#Override
protected Void doInBackground(Void... params)
{
Log.d(TAG, "doInBackground() -- Here is the download");
// downloadBitmap("http://mydomain.com/image.jpg")
return null;
}
#Override
protected void onPostExecute(Void res)
{
Log.d(TAG, "onPostExecute()");
if(isCancelled()){
return;
}
}
}.execute();
}
#Override
protected void onDestroy()
{
super.onDestroy();
task.cancel(true);
}
}
UPDATE:
I have tested using a combination of traditional Threads and runOnUiThread method, and it seems to work better. Now the thread runs every time.
Removing the AsyncTask and using a traditional Thread instead of combining it with runOnUiThread seems to work. But I still have not found the reason why the AsyncTask is so "unstable".
Here is the code that works for me:
public class ImageActivity extends Activity {
private Thread worker;
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.main);
worker = new Thread(new Runnable(){
private void updateUI(final List<Object> list)
{
if(worker.isInterrupted()){
return;
}
runOnUiThread(new Runnable(){
#Override
public void run()
{
// Update view and remove loading spinner etc...
}
});
}
private List<Object> download()
{
// Simulate download
SystemClock.sleep(1000);
return new ArrayList<Object>();
}
#Override
public void run()
{
Log.d(TAG, "Thread run()");
updateUI(download());
}
});
worker.start(); }
#Override
protected void onDestroy()
{
super.onDestroy();
worker.interrupt();
}
}
I ran into similar problem. You can't have multiple Asynctasks running in parallel up until SDK 11. Check here for more info
I just ran into this problem as well. If you use AsyncTask.execute, your task is run on a serial queue (from the Android 4.3 source):
When first introduced, AsyncTasks were executed serially on a single
background thread. Starting with DONUT, this was changed to a pool of
threads allowing multiple tasks to operate in parallel. Starting with
HONEYCOMB, tasks are executed on a single thread to avoid common
application errors caused by parallel execution.
This is consistent with behavior that I saw. I had an AsyncTask popped up a dialog in doInBackground and blocked until the dialog was closed. The dialog needed its own AsyncTask to complete. The dialog's AsyncTask.doInBackground method never executed because the original AsyncTask was still blocked.
The solution is to execute the second AsyncTask in a separate Executor.
Use traceview to investigate -- or obtain a thread dump. My guess is that one of your AsyncTask threads are hanging on downloading.
AsyncTask has a small thread pool, so if one of your tasks hangs, it could end up blocking your thread pool.
Here's a quick test you can run -- on 4.3, I see that I have only 5 concurrent threads I can run. When one thread exits, other threads start up.
private void testAsyncTasks() {
for (int i = 1; i <= 10; i++) {
final int tid = i;
new AsyncTask<Integer, Void, Void>() {
protected void onPreExecute() {
Log.d("ASYNCTASK", "Pre execute for task : " + tid);
};
#Override
protected Void doInBackground(Integer... args) {
int taskid = args[0];
long started = SystemClock.elapsedRealtime();
Log.d("ASYNCTASK", "Executing task: " + taskid + " at " + started);
for (int j = 1; j <= 20; j++) {
Log.d("ASYNCTASK", " task " + taskid + ", time=" + (SystemClock.elapsedRealtime() - started));
SystemClock.sleep(1000);
}
return null;
}
protected void onPostExecute(Void result) {
Log.d("ASYNCTASK", "Post execute for task : " + tid);
};
}.execute(i);
}
}
You shouldn't have to worry about housekeeping thread in Android as it is managed by the system.
Please also post the image download method. Have you also tried to not cancel the thread in the onDestroy() method? How are you returning the the image to your UI thread?
The problem I believe is with the heavy image download task. Even if you cancel the async task the image download will continue to execute and the async task does not finish until the download is complete. You might want to check the isCancelled() method on AyncTask while the download is going on and kill the download if the task is cancelled.
For reference, heres the documentation on cancel() method :
Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.
Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.
I had this too, no real reason for not starting. I've noticed that after restarting adb it worked again. Not sure why this is, but it did work for me

Categories

Resources