问题
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.
回答1:
Have you updated your data set? It could be the case that the cursor has been re-loaded due notifying a change in the content resolver:
getContentResolver().notifyChange(URI, null);
If you have set a notification URI, this would trigger your current cursor to close and a new cursor to be returned by the cursor loader. You can then grab the new cursor if you have registered a onLoadCompleteListener:
mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
@Override
public void onLoadComplete(Loader<Cursor> loader, Cursor cursor) {
// Set your listview's CursorAdapter
}
});
回答2:
You can try to path null instead cursor into adapter constructor. Then owerride SwapCursor(Cursor c) in adapter, move initialization of cursor data there and call it in OnLoadFinished(Loader loader, Cursor data) method of your data loader.
enter code here
@Override
public void onActivityCreated(Bundle savedInstanceState) {
// ... building your query here
mSimpleCursorAdapter = new mSimpleCursorAdapter(getActivity().getApplicationContext(),
layout, null, from, to, flags);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
contentAdapter.swapCursor(data);
}
来源:https://stackoverflow.com/questions/14956608/illegalstateexception-attempt-to-re-open-an-already-closed-object-in-simplecur