Fix cursor leaks when canceling AsyncTask

We discovered that AsyncTask.cancel() doesn't quite perform as expected;
In particular, if you call cancel() during a particularly slow background
worker, the result is discarded and onPostExecute() is never called.  If
the result is an open cursor, then we "leak" by not closing it.

For AccountFolderList, which has a multi-step doInBackground():
1.  Check for isCancelled() during the long doInBackground() which will
    reduce the number of discarded cursors in the first place.
2.  Check for isCancelled() at the end of the long doInBackground() and
    if true, close the result cursors and return null.
3.  In the existing isCancelled() code in onPostExecute(), close the
    cursors.

For other AsyncTasks (with simpler configurations):
1.  Check for isCancelled() at the end of doInBackground() and if true,
    close the just-opened cursor and return null.

See also Change-Id: Ie63a3197af563baa8bb0fe6f1ef9423e281cbf4c
Bug: 3088870

Change-Id: I51b11bb5b23f209f8b61962500a063040484fd24
This commit is contained in:
Andy Stadler 2011-01-06 13:35:57 -08:00
parent 201a24f51e
commit 29d79adf4b
3 changed files with 41 additions and 9 deletions

View File

@ -386,19 +386,46 @@ public class AccountFolderListFragment extends ListFragment
@Override
protected Object[] doInBackground(Void... params) {
// Create the summaries cursor
Cursor c1 = getSummaryChildCursor();
Cursor c1 = null;
Cursor c2 = null;
Long defaultAccount = null;
if (!isCancelled()) {
// Create the summaries cursor
c1 = getSummaryChildCursor();
}
// TODO use a custom projection and don't have to sample all of these columns
Cursor c2 = mActivity.getContentResolver().query(
EmailContent.Account.CONTENT_URI,
EmailContent.Account.CONTENT_PROJECTION, null, null, null);
Long defaultAccount = Account.getDefaultAccountId(mActivity);
if (!isCancelled()) {
// TODO use a custom projection and don't have to sample all of these columns
c2 = mActivity.getContentResolver().query(
EmailContent.Account.CONTENT_URI,
EmailContent.Account.CONTENT_PROJECTION, null, null, null);
}
if (!isCancelled()) {
defaultAccount = Account.getDefaultAccountId(mActivity);
}
if (isCancelled()) {
if (c1 != null) c1.close();
if (c2 != null) c2.close();
return null;
}
return new Object[] { c1, c2 , defaultAccount};
}
@Override
protected void onPostExecute(Object[] params) {
if (isCancelled() || params == null || ((Cursor)params[1]).isClosed()) {
if (isCancelled() || params == null) {
if (params != null) {
Cursor c1 = (Cursor)params[0];
if (c1 != null) {
c1.close();
}
Cursor c2 = (Cursor)params[1];
if (c2 != null) {
c2.close();
}
}
return;
}
// Before writing a new list adapter into the listview, we need to

View File

@ -131,7 +131,7 @@ public class AccountShortcutPicker extends ListActivity
@Override
protected void onPostExecute(Cursor cursor) {
// If canceled, get out immediately
if (cursor == null) {
if (cursor == null || cursor.isClosed()) {
return;
}
// If there are no accounts, we should never have been here - exit the activity

View File

@ -266,7 +266,12 @@ public class MessageOrderManager {
private class LoadMessageListTask extends AsyncTask<Void, Void, Cursor> {
@Override
protected Cursor doInBackground(Void... params) {
return openNewCursor();
Cursor c = openNewCursor();
if (isCancelled()) {
c.close();
c = null;
}
return c;
}
@Override