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.
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 theMESSAGE_POST_RESULT
message from the background callsonPostExecute()
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 myonPostExecute()
logic.The Gingerbread code receives
MESSAGE_POST_RESULT
and callsfinish()
. Thenfinish()
callsonPostExecute()
.