I am seeing some strange behaviour with onPause / onResume in my app and cannot work out what is happening.
I perform a database query (simple subclass of AsyncTask) in onResume and cancel it in onPause if it is still executing. I received a crash report that made me wonder if the task cancel was working or not so added an analytics event to record onPostExecute getting called after onPause had cancelled the task.
Over the last month I have seen 140 of these events for 4,100 page views.
#Override
protected void onResume() {
super.onResume();
mIsResumed = true;
if (mReverseCardsTask == null) {
mReverseCardsTask = new TcgCursorTask(this) {
#Override
protected Cursor doInBackground(Void... params) {
return mDb.reverseFetchCards();
}
#Override
protected void onPostExecute(Cursor cursor) {
if (mIsResumed) {
onReverseCardsCursor(cursor);
} else {
EasyTracker.getTracker().sendEvent("error", "on-post-execute", "called after paused", 1L);
}
}
};
mReverseCardsTask.execute();
}
}
#Override
protected void onPause() {
super.onPause();
mIsResumed = false;
if (mReverseCardsTask != null) {
mReverseCardsTask.cancel(false);
mReverseCardsTask = null;
}
}
I have a feeling I am missing something very simple here, but can't see it.
I just noticed I am not clearing mReverseCardsTask in onPostExecute, but that should not matter.
Just calling cancel() doesn't do anything. You actually have to put checks in the process to determine if it is to be canceled and do the job of canceling it.
The OS doesn't know what you may need to do to clean things up (like closing files or open network connections) before stopping.
OK. I have worked it out. I am not sure which API version it was fixed in, but if you look at the code for Gingerbread there is a clear race condition in the cancel() handling. The GUI thread code which processes the MESSAGE_POST_RESULT message from the background calls onPostExecute() regardless of whether or not the task was cancelled.
It turns out that the fix is quite simple. All I need to do is add my own check of isCancelled() before executing my onPostExecute() logic.
The Gingerbread code receives MESSAGE_POST_RESULT and calls finish(). Then finish() calls onPostExecute().
Related
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
I have used AsyncTasks with my application, in order to lazy download and update the UI.
For now my AsyncTasks updates the UI real simply:
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
gender.setText(values[0]);
}
My problem is how to check if the activity which the gender TextView rendered from, is still available?
If not, I will get an error and my application will shut down.
You can cancel your asynctask in the activity's onDestroy
#Override
protected void onDestroy() {
asynctask.cancel(true);
super.onDestroy();
}
and when performing changes you check whether your asynctask has been cancelled(activity destroyed) or not
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
if(!isCancelled()) {
gender.setText(values[0]);
}
}
I had a similar problem - essentially I was getting a NPE in an async task after the user had destroyed the fragment. After researching the problem on Stack Overflow, I adopted the following solution:
volatile boolean running;
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
running=true;
...
}
public void onDestroy() {
super.onDestroy();
running=false;
...
}
Then, I check "if running" periodically in my async code. I have stress tested this and I am now unable to "break" my activity. This works perfectly and has the advantage of being simpler than some of the solutions I have seen on SO.
Try
if (!isFinishing()) {
gender.setText(values[0]);
}
Check whether activity is running or not
if (!isFinishing()) {
// Do whatever you want to do
}
I will insist that you that if you Activity is not running why don't you cancel the AsyncTask?
That would be a better and feasible solution. If you Application is running say you move from one Activity to another then it won't give error AFAIK.
But, I would insist to cancel the AsyncTask then you'r Activity is not running, you can check AsyncTask is running or not,
if(task != null && task.equals(AsyncTask.Status.RUNNING))
task.cancel(true);
Even though, I have never faced this scenario; I will try to answer your question.
In your case you will need to validate the Context passed to AsyncTask.
You can perform validation
if(null!=mContext) //Activity still exist!!
{
gender.setText(values[0]);
}
else //Activity is destroyed
{
//Take appropriate action!!
}
The advantage will be, if the activity is destroyed by the time you reach this statement, your Context will automatically become null and you can handle the scenario.
As this part of one training on Android Developers suggests, keep a WeakReference on the UI element that needs to be updated after task is done and check if the reference is null before using it. This helps not only in checking if the UI is still around, but also does not prevent UI elements from being garbage collected.
Shouldn't
if (gender) {
gender.setText(values[0]);
}
be enough?
Just wondered what's happened with IceCream Sandwich...
I had a simple AsyncTask reading data from a server. When the Disconnect button was clicked, then lServerTask.cancel(true); was called which was successfully calling onCancelled(), setting m_Running = false; thus breaking out of the while loop in protected void doInBackground(Void...params).
With my Galaxy Nexus, I click the disconnect button, but the task does not quit. And is still connected to the server. Does anyone have any ideas?
Many Thanks
Mark
Code FYI -
// automatically done on worker thread (separate from UI thread)
protected Void doInBackground(Void...params)
{
while(m_Running)
{
try
{
lRDS.readSocket();
}
catch (IOException e)
{
lRDS.disconnectFromServer();
publishProgress(e.toString());
return null;
}
lDataBuffer = lRDS.getDataBuffer();
publishProgress(lDataBuffer);
}
return null;
}
#Override
protected void onCancelled()
{
m_Running = false;
connectRDSButton.setEnabled(true);
disconnectRDSButton.setEnabled(false);
}
and from the UI side:
private class disconnectRDSButtonHandler implements View.OnClickListener
{
public void onClick(View v)
{
editRobotData.setText("Disconnect...");
if (lRobotDataServerTask.cancel(true) == true)
{
editRobotData.setText("Disconnected...");
}
}
}
Okay it seems I've found the issue. I needed to check isCancelled() in the while loop of the doInBackground() method and then call the onCancelled() method from there. Not sure why I didn't see this as a problem on android 2.2 on HTC desire.
protected Void doInBackground(Void...params)
{
while(m_Running)
{
if (isCancelled () == true)
{
onCancelled();
}
Hope this helps someone.
Mark
There are two approaches to cancel an AsyncTask:
Checking isCancelled inside your doInBackground method to try to terminate as soon as possible.
Calling cancel(true), then AsyncTask's background thread will be interrupted. For this to work, you'll need to strategically place Thread.sleep calls in your doInBackground method, and when you catch the InterruptedException, you return.
Of these two approaches, the latter is the most unreliable by far, since any call to a method catching an InterruptedException other than yours will screw the cancellation. For instance, if you call a method such as SystemClock.sleep that swallows the exception, you'll never hit your catch clause.
In both cases, long operations in between isCancelled checks or Thread.sleep calls will run to completion.
Now about your question, you were using the second approach, but there were not any Thread.sleep call, so it wont work unless some of the methods you are calling catched the Interrupted exception and throwed the IOException. Probably changes in implementation from a version to another make the difference.
I need my Android app to periodically fetch data from a server using AJAX calls, and update the UI accordingly (just a bunch of TextViews that need to be updated with setText()). Note that this involves 2 tasks:
Making an AJAX call, and updating the UI once I receive a response - I use a simple AsyncTask for this.
Doing the above repeatedly, at regular intervals.
I haven't figured out an elegant way to achieve Point 2 above. Currently, I am simply executing the task itself from OnPostExecute(). I read on this thread at SO that I need not worry about garbage collection as far as the AsyncTask objects are concerned.
But I'm still unsure as to how I set up a timer that will fire my AsyncTask after it expires. Any pointers will be appreciated. Here is my code:
public class MyActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new AjaxRequestTask().execute(MY_REST_API_URL);
}
private void updateReadings(String newReadings) {
//Update the UI
}
class AjaxRequestTask extends AsyncTask<String, Integer, String> {
#Override
protected String doInBackground(String... restApiUrl) {
//Do AJAX Request
}
#Override
protected void onPostExecute(String result) {
updateReadings(result);
/*Is there a more elegant way to achieve this than create a new AsyncTask object every 10 seconds? Also, How can I update the UI if I create a timer here? */
new AjaxRequestTask().execute(MY_REST_API_URL);
}
}
}
Thanks in advance
EDIT:
I tried posting an answer but couldn't do it since I don't have the reputation to answer within 8 hours.
Well, so I found a solution. I'm not convinced however.
protected void onPostExecute(String result) {
updateReadings(result);
// super.onPostExecute(result);
new Timer().schedule(
new TimerTask() {
#Override
public void run() {
new AjaxRequestTask().execute(MY_REST_API_URL);
}
},
TIMER_ONE_TIME_EXECUTION_DELAY
);
}
Are there any flip sides that I should be aware of when I use this? In particular, I am seeing lots of GCs happening in the LogCat. Also, I am wondering how an AsyncTask can be candidate for GC unless the onPostExecute() completes?
How can I "stop" the updates? One way I thought of was to make the very first AsyncTask instance as a member variable of the Activity. That way, I can invoke cancel(true) on it and hope that this will "stop" the tasks.
SOLUTION:
In case anyone is looking for something similar - none of the solutions I mentioned here work satisfactorily. They all suffer from OutOfMemory issues. I did not debug into the details of the OOM, but I suspect it could either be because of the recursion, or because of having HTTP-related objects as member variables in the AsyncTask rather than as members of the Activity (basically because of NOT reusing HTTP and other objects).
I discarded this approach for a different one - making my Ajax Calls endlessly in the doInBackground() of my AsyncTask; and updating the UI in onProgressUpdate(). That way I also avoid the overhead of maintaining too many threads or Handlers for updating the UI (remember UI can be updated in onProgressUpdate() ).
This approach also eliminates the need for Timers and TimerTasks, favoring the use of Thread.sleep() instead. This thread on SO has more details and a code snippet too.
Call postDelayed() on any View to schedule a hunk of code to be run on the main application thread after a certain delay. Do this in onPostExecute() of the AsyncTask to create and execute another AsyncTask.
You could use AlarmManager, as others have cited, but I would agree with you that it feels a bit like overkill for timing that occurs purely within an activity.
That being said, if the AJAX calls should be occurring regardless of whether the activity exists, definitely consider switching to AlarmManager and an IntentService.
I think the android way to do this is using AlarmManager. Or you can user a basic java Timer as well. I'd recommend AlarmManager.
Set it up to send some intent with a custom Action, and register a broadcastreceiver for it.
If the ajax calls are only executed in the activity you can just use a timer in the activity which starts the tasks.
Otherwise use a service which uses the AlarmManager and which connects to the gui via a broadcast.
The recommended way to do a repeated task, is via AlarmManager, as alluded to by Scythe. Basically it involves setting up a broadcast listener, and having AlarmManager fire off an intent to that listener at whatever interval you choose. You then would have your broadcast listener call out to the activity to run the AsyncTask. If you need a very tight timer (less than 5s calls I'd say), then you're better off using a Timer within a Service, and using AIDL to call back to the activity.
Instead of talking directly from the broadcast intent, you could also setup an IntentService which you can poke, and use AIDL to update the activity.
This is how I achieved it finally. Note that the AsyncTask cancel(true) method is useless in my scenario because of the recursion. I used what #CommonsWare suggested - used a flag to indicate whether any more tasks should be executed.
public class MyActivity extends Activity {
/*Flag which indicates whether the execution should be halted or not.*/
private boolean mCancelFlag = false;
private AjaxRequestTask mAjaxTask;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if(mAjaxTask == null){
mAjaxTask = new AjaxRequestTask();
}
mAjaxTask.execute(MY_REST_API_URL);
}
#Override
protected void onResume() {
super.onResume();
mCancelFlag = false; /*when we resume, we want the tasks to restart. Unset cancel flag*/
/* If the main task is Finished, create a new task and execute it.*/
if(mAjaxTask == null || mAjaxTask.getStatus().equals(AsyncTask.Status.FINISHED)){
new AjaxRequestTask().execute(TLS_REST_API_URL);
}
}
#Override
protected void onPause() {
mCancelFlag = true; /*We want the execution to stop on pause. Set the cancel flag to true*/
super.onPause();
}
#Override
protected void onDestroy() {
mCancelFlag = true;/*We want the execution to stop on destroy. Set the cancel flag to true*/
super.onDestroy();
}
private void updateReadings(String result) {
//Update the UI using the new readings.
}
class AjaxRequestTask extends AsyncTask<String, Integer, String> {
private AjaxRequestTask mChainAjaxRequest;
private Timer mTimer;
private TimerTask mTimerTask;
#Override
protected String doInBackground(String... restApiUrl) {
//Do AJAX call and get the response
return ajaxResponse;
}
#Override
protected void onPostExecute(String result) {
Log.d(TAG, "Updating readings");
updateReadings(result);
// super.onPostExecute(result);
if(mTimer == null){
mTimer = new Timer();
}
if(!mCancelFlag){/*Check if the task has been cancelled prior to creating a new TimerTask*/
if(mTimerTask == null){
mTimerTask = new TimerTask() {
#Override
public void run() {
if(!mCancelFlag){/*One additional level of checking*/
if(mChainAjaxRequest == null){
mChainAjaxRequest = new AjaxRequestTask();
}
mChainAjaxRequest.execute(MY_REST_API_URL);
}
}
};
}
mTimer.schedule(mTimerTask,TIMER_ONE_TIME_EXECUTION_DELAY);
}
}
}
}
In my Activity I use multiple AsyncTask classes.
How to cancel AsyncTask when Activity finishes?
i think the best place to do this is onStop
protected void onStop() {
super.onStop();
/*
* The device may have been rotated and the activity is going to be destroyed
* you always should be prepared to cancel your AsnycTasks before the Activity
* which created them is going to be destroyed.
* And dont rely on mayInteruptIfRunning
*/
if (this.loaderTask != null) {
this.loaderTask.cancel(false);
}
}
in my Task i then check as often as possible if cancel was called
protected String doInBackground(String... arg0) {
if (this.isCancelled()) {
return null;
}
}
and of course dont forget to drop data that maybe returned since there's no more Activity to receive it
protected void onPostExecute(List<UserStatus> result) {
if(!this.isCancelled()) {
//pass data to receiver
}
}
I don't understand if your "cancel" means rollback but you have a cancel method on the AsyncTask class.
The asynctask thread is kept alive in a thread pool for future istances of AsyncTask. You can't remove them.