I have a series of ListView objects in Fragments that are being populated by a CursorAdapter which gets a Cursor from the LoaderManager for the activity. As I understand it, all database and Cursor close actions are completely handled by the LoaderManager and the ContentProvider, so at no point in any of the code am I calling .close() on anything.
Sometimes, however, I get this exception:
02-19 11:07:12.308 E/AndroidRuntime(18777): java.lang.IllegalStateException: attempt to re-open an already-closed object: android.database.sqlite.SQLiteQuery (mSql = SELECT * FROM privileges WHERE uuid!=?)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:33)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:82)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:164)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:147)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:178)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.database.CursorWrapper.moveToPosition(CursorWrapper.java:162)
02-19 11:07:12.308 E/AndroidRuntime(18777): at android.widget.CursorAdapter.getView(CursorAdapter.java:241)
I put some log code into my CursorAdapter that tells me when getView(...), getItem(...) or getItemId(...) are being called and it seems as though this is happening on the first getView(...) for a given adapter after a lot of getView(...)s for another adapter. It also happens after a user has navigated around the app a lot.
This makes me wonder if the Cursor for an adapter is being retained in the CursorAdapter, but being closed in error by the ContentProvider or the Loader. Is this possible? Should I be doing any housekeeping on the CursorAdapter based on app/activity/fragment lifecycle events?
ContentProvider query method:
class MyContentProvider extends ContentProvider {
//...
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor query = db.query(getTableName(uri), projection, selection, selectionArgs, null, null, sortOrder);
query.setNotificationUri(getContext().getContentResolver(), uri);
return query;
}
//...
}
Typical LoaderCallbacks:
LoaderCallbacks<Cursor> mCallbacks = new LoaderCallbacks<Cursor>() {
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mArticleAdapter.swapCursor(null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if(cursor.isClosed()) {
Log.d(TAG, "CURSOR RETURNED CLOSED");
Activity activity = getActivity();
if(activity!=null) {
activity.getLoaderManager().restartLoader(mFragmentId, null, mCallbacks);
}
return;
}
mArticleAdapter.swapCursor(cursor);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
triggerArticleFeed();
CursorLoader cursorLoader = null;
if(id == mFragmentId) {
cursorLoader = new CursorLoader(getActivity(),
MyContentProvider.ARTICLES_URI,
null,
ArticlesContentHelper.ARTICLES_WHERE,
ArticlesContentHelper.ARTICLES_WHEREARGS,
null);
}
return(cursorLoader);
}
};
CursorAdapter constructor:
public ArticlesCursorAdapter(Context context, Cursor c) {
super(context, c, 0);
mImageloader = new ImageLoader(context);
}
I have read this question but unfortunately it hasn't got the answer to my problem as it simply suggests using a ContentProvider, which I am.
IllegalStateException: attempt to re-open an already-closed object. SimpleCursorAdapter problems
IMPORTANT NEW INFORMATION THAT HAS JUST COME TO LIGHT
I discovered some other code elsewhere in the project that was NOT using Loaders and NOT managing its Cursors properly. I've just switched this code over to use the same pattern as above; however, if this fixes things, it would suggest that an unmanaged Cursor in one part of a project can kill a properly managed one elsewhere.
Stick around.
OUTCOME OF NEW INFORMATION
That did not fix it.
NEW IDEA
@Override
onDestroyView() {
getActivity().getLoaderManager().destroyLoader(mFragmentId);
//any other destroy-time code
super.onDestroyView()
}
ie possibly yes, I should be doing housekeeping on the CursorAdapter (or rather the CursorLoader in line with lifecycle events).
OUTCOME OF NEW IDEA
Nope.
PREVIOUS IDEA
Turned out to work once I added in a minor tweak! However it's so complex that I should probably rewrite the entire question.