This is a follow up to my question here: How to close a cursor used in a for loop
The responses solved the "Cursor finalized without prior close" warning but it has caused a StaleDataException in a very particular situation.
If the list has been scrolled, this cursor closed...
Cursor cursor = null;
cursor = (Cursor) getListView().getItemAtPosition(n);
//do something
if(cursor != null) {
cursor.close();
}
and the fragment backgrounded I get the following error:
09-15 21:16:58.240: E/test(21621): Exception
FATAL EXCEPTION: main
android.database.StaleDataException: Attempted to access a cursor after it has been closed.
at android.database.BulkCursorToCursorAdaptor.throwIfCursorIsClosed(BulkCursorToCursorAdaptor.java:64)
at android.database.BulkCursorToCursorAdaptor.getCount(BulkCursorToCursorAdaptor.java:70)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:196)
at android.database.CursorWrapper.moveToPosition(CursorWrapper.java:162)
at android.support.v4.widget.CursorAdapter.getItemId(CursorAdapter.java:225)
at android.widget.AbsListView.onSaveInstanceState(AbsListView.java:1782)
at android.view.View.dispatchSaveInstanceState(View.java:11950)
at android.view.ViewGroup.dispatchFreezeSelfOnly(ViewGroup.java:2685)
at android.widget.AdapterView.dispatchSaveInstanceState(AdapterView.java:782)
at android.view.ViewGroup.dispatchSaveInstanceState(ViewGroup.java:2671)
at android.view.View.saveHierarchyState(View.java:11933)
at android.support.v4.app.FragmentManagerImpl.saveFragmentViewState(FragmentManager.java:1608)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1004)
at android.support.v4.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1212)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:639)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1478)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:446)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4793)
This question seems to relate to a similar issue: Merging cursors during onLoadFinished() causes StaleDataException after rotation but it relates to merging cursors and suggests using swapCursor, I'm not sure how I would apply that to this situation.
My thoughts are that getListView().getItemAtPosition(n)
must be returning a reference to the cursor rather than a new cursor and so when the activity is backgrounded and tries to access the now closed cursor whilst saving the fragment state it crashes. As mentioned earlier, it only crashes if the list view has been scrolled, I'm not sure why that should affect it.
How do I correctly close the cursor without causing a crash?
EDIT code in response to a comment asking to see how the cursor was loaded:
String[] desired_columns = {
MediaStore.Audio.Media._ID, //this column is needed, even though it won't be displayed so the cursor can populate the listview
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.TITLE,
MediaStore.Audio.Media.DURATION
};
String selectionStatement = MediaStore.Audio.Media.IS_MUSIC + " != ? AND " + MediaStore.Audio.Media.DURATION + " > ?";
String[] selectionArguments = new String[2];
selectionArguments[0] = "0";
selectionArguments[1] = "7000";
Cursor myCursor = getActivity().getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
desired_columns,
selectionStatement, //selection criteria
selectionArguments, //selection arguments like with SQL PDO use ? in criteria and put user input here to avoid sql injection
MediaStore.Audio.AudioColumns.ARTIST + " ASC"); //sort order of results
//moveToFirst() returns false if the cursor is empty
if (myCursor != null && myCursor.moveToFirst()) {
customCursorAdapter myCursorAdapter = new customCursorAdapter(getActivity(), myCursor, 0);
setListAdapter(myCursorAdapter);
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
getListView().setSelector(R.drawable.list_selection_colouring);
}
I found a solution which avoids the deprecated manageQuery and also doesn't use CursorLoaders.
The solution was to close the cursor within onDestroy(). I tried closing it within onPause but this didn't help. Looking at the fragment lifecycle http://developer.android.com/guide/components/fragments.html#Lifecycle onDestroy is almost the last method called so the fragments state will already have been saved, making it safe to close the cursor.
Here is the code I used:
@Override
public void onDestroy() {
super.onDestroy();
if (cursor != null) {
cursor.close();
}
}