开发者

Android error: java.lang.IllegalStateException: trying to requery an already closed cursor

environment (Linux/Eclipse Dev for Xoom Tablet running HoneyComb 3.0.1)

In my app I'm using the camera (startIntentForResult()) to take a picture. After the picture is taken I get the onActivityResult() callback and am able to load a Bitmap using a Uri passed via the "take picture" intent. At that point my activity is resumed and I get an error trying to reload the images into a gallery:

FATAL EXCEPTION: main
ERROR/AndroidRuntime(4148): java.lang.RuntimeException: Unable to resume activity {...}: 
 java.lang.IllegalStateException: trying to requery an already closed cursor
     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2243)
     at android.app.ActivityThread$H.开发者_运维百科handleMessage(ActivityThread.java:1019)
     at android.os.Handler.dispatchMessage(Handler.java:99)
     at android.os.Looper.loop(Looper.java:126)
     at android.app.ActivityThread.main(ActivityThread.java:3997)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:491)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:841)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:599)
     at dalvik.system.NativeStart.main(Native Method)
 Caused by: java.lang.IllegalStateException: trying to requery an already closed cursor
     at android.app.Activity.performRestart(Activity.java:4337)
     at android.app.Activity.performResume(Activity.java:4360)
     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2205)
     ... 10 more

The only cursor logic I'm using is that after the image is taken I convert the Uri to a file using the following logic

String [] projection = {
    MediaStore.Images.Media._ID, 
    MediaStore.Images.ImageColumns.ORIENTATION,
    MediaStore.Images.Media.DATA 
};

Cursor cursor = activity.managedQuery( 
        uri,
        projection,  // Which columns to return
        null,        // WHERE clause; which rows to return (all rows)
        null,        // WHERE clause selection arguments (none)
        null);       // Order-by clause (ascending by name)

int fileColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
if (cursor.moveToFirst()) {
    return new File(cursor.getString(fileColumnIndex));
}
return null;

Any ideas what I'm doing wrong?


Looks like the managedQuery() call is deprecated in the Honeycomb API.

Doc for managedQuery() reads:

This method is deprecated.
Use CursorLoader instead.

Wrapper around query(android.net.Uri, String[], String, String[], String) 
that the resulting Cursor to call startManagingCursor(Cursor) so that the
activity will manage its lifecycle for you. **If you are targeting HONEYCOMB 
or later, consider instead using LoaderManager instead, available via 
getLoaderManager()**.

Also I noticed that I was calling cursor.close() after the query which I guess is a no-no. Found this really helpful link as well. After some reading I came up with this change that seems to work.

// causes problem with the cursor in Honeycomb
Cursor cursor = activity.managedQuery( 
        uri,
        projection,  // Which columns to return
        null,        // WHERE clause; which rows to return (all rows)
        null,        // WHERE clause selection arguments (none)
        null);       // Order-by clause (ascending by name)

// -------------------------------------------------------------------

// works in Honeycomb
String selection = null;
String[] selectionArgs = null;
String sortOrder = null;

CursorLoader cursorLoader = new CursorLoader(
        activity, 
        uri, 
        projection, 
        selection, 
        selectionArgs, 
        sortOrder);

Cursor cursor = cursorLoader.loadInBackground();


For the record, here's how I fixed this in my code (which runs on Android 1.6 and up): The problem in my case was that I was inadvertently closing managed cursors by calling CursorAdapter.changeCursor(). Calling Activity.stopManagingCursor() on the adapter's cursor before changing the cursor solved the problem:

// changeCursor() will close current one for us: we must stop managing it first.
Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); // *** adding these lines
stopManagingCursor(currentCursor);                                          // *** solved the problem
Cursor c = db.fetchItems(selectedDate);
startManagingCursor(c);
((SimpleCursorAdapter)getListAdapter()).changeCursor(c);


FIX: use context.getContentResolver().query instead of activity.managedQuery.

Cursor cursor = null;
try {
    cursor = context.getContentResolver().query(uri, PROJECTION, null, null, null);
} catch(Exception e) {
    e.printStackTrace();
}
return cursor;


I've created this question here as I could not comment on the last answer (comments disabled for some reason). I thought opening a new thread on this will only complicate things.

I get application crashes when I go from Activity A to Activity B and then going back to Activity A. This doesn't happen all the time - only sometimes and I am having hard time finding EXACTLY where this happens. All happens on the same device (Nexus S) but I don't believe this is a device issue.

I have few questions regarding @Martin Stine's answer.

  • In the documentation it says about changeCursor(c);: "Change the underlying cursor to a new cursor. If there is an existing cursor it will be closed". So why do I have to stopManagingCursor(currentCursor); - isn't that redundant?
  • When I use the code offered by @Martin Stine I get a null pointer exception. The reason for that is that in the first "run" of the application ((SimpleCursorAdapter)getListAdapter()) will evaluate to NULL because no cursor was yet to be created. Sure I could check if I DON'T get a null and only then try stopping to manage the cursor but finally I decided to place my `stopManagingCursor(currentCursor); in the onPause() method of this activity. I thought this way I will for sure have a cursor to stop managing and I should do it just before I leave the Activity to a different one. The issue - I am using several cursors (one to fill up a text of an EditText field and the other for a list view) in my activity I guess not all of them are related to a ListAdapter cursor -
    • How do I know which one to stop managing? If I have 3 different list views?
    • Should I close all of them during onPause()?
    • How do I get a list of all my opened cursors?

So many questions... Hope anyone could help.

When I get to onPause() I do have a cursor to stop managing but I am yet to determine if this solves the problem as this error shows up sporadically.

Many thanks!


AFTER SOME INVESTIGATION:

I found something interesting that might give the answer to the "mysterious" side of this issue:

Activity A uses two cursors: one to fill up an EditText field. The other is to populate a ListView.

When moving from Activity A to Activity B and coming back, the field + ListView in Activity A must be filled up again. It seems like the EditText field will never have an issue with that. I couldn't find a way to get the EditText field's current cursor (like in Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();) and reason tells me that the EditText field will not keep it. On the other hand the ListView will "remember" its cursor from last time (from before Activity A -> Activity B). In addition, and this is the strange thing, the Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); will have a different ID after Activity B -> Activity A and all this WITHOUT me ever calling

Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();
stopManagingCursor(currentCursor);  

I guess in some cases, when the system needs to free resources, the cursor will be killed and when Activity B -> Activity A, the system will still try to use this old dead cursor, which will result in an Exception. And in other cases the system will come up with a new cursor that is still alive and thus no Exception will occur. This might explain why this shows up only sometimes. I guess this is hard to debug due to the difference of application speed when either running OR debugging the application. When debugging, it takes more time and thus might give the system time to come up with a new cursor or vice versa.

In my understanding this makes the usage of

Cursor currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor();
stopManagingCursor(currentCursor);

As recommended by @Martin Stine a must in SOME cases and redundant in OTHERS: if I return to the method and the system is trying to use a dead cursor, one must create a new cursor and replace it in the ListAdapter, otherwise I get angry app users with a crashed app. In another case when the system will find itself a new cursor - the lines above are redundant as they invalidate a good cursor and create a new one.

I guess in order to prevent this redundancy I would need something like this:

ListAdapter currentListAdapter = getListAdapter();
Cursor currentCursor = null;
 Cursor c = null;

//prevent Exception in case the ListAdapter doesn't exist yet
if(currentListAdapter != null)
    {
        currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor();

                    //make sure cursor is really dead to prevent redundancy
                    if(currentCursor != null)
                    {
                        stopManagingCursor(currentCursor);

                        c = db.fetchItems(selectedDate);

                        ((SimpleCursorAdapter)getListAdapter()).changeCursor(c);
                    }
                    else
                    {
                      c = db.fetchItems(selectedDate);

                    }
    }
            else
            {
              c = db.fetchItems(selectedDate);

            }

startManagingCursor(c);

I would love to hear what you think about this!


Just add the following code at the end of the cursor block..

   try {
                Cursor c = db.displayName(number);

                startManagingCursor(c);
                if (!c.moveToFirst()) {
                    if (logname == null)
                        logname = "Unknown";
                    System.out.println("Null " + logname);
                } else {
                    logname = c.getString(c
                            .getColumnIndex(DataBaseHandler.KEY_NAME));
                    logdp = c.getBlob(c
                            .getColumnIndex(DataBaseHandler.KEY_IMAGE));
                    // tvphoneno_oncall.setText(logname);
                    System.out.println("Move name " + logname);
                    System.out.println("Move number " + number);
                    System.out.println("Move dp " + logdp);
                }

                stopManagingCursor(c);
            } 


This problem plagued me for a long time and I finally came up with a simple solution that works like a charm on all versions of Android. First, don't use startManagingCursor() since it is evidently buggy and deprecated in any case. Secondly, close a Cursor as soon as possible after you are done with it. I use try and finally to insure that the Cursor gets closed under all circumstances. If your method must return a Cursor then the calling routine is responsible for closing it as soon as possible.

I used to leave cursors open for the lifetime of an Activity but I have since abandoned that for this transactional approach. Now my app is very stable and does not suffer from "Android error: java.lang.IllegalStateException: trying to requery an already closed cursor" when switching Activities even though they access the same database.

   static public Boolean musicReferencedByOtherFlash(NotesDB db, long rowIdImage)
   {
      Cursor dt = null;
      try
      {
         dt = db.getNotesWithMusic(rowIdImage);
         if (   (dt != null)
             && (dt.getCount() > 1))
            return true;
      }
      finally
      {
         if (dt != null)
            dt.close();
      }
      return false;
   }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜