I have a series of ListView
objects in Fragment
s 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 Loader
s and NOT managing its Cursor
s 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.