Android错误:java.lang.IllegalStateException:尝试重新查询已经关闭的游标

时间:2011-05-06 18:33:32

标签: android cursor illegalstateexception android-loadermanager

环境(运行HoneyComb 3.0.1的Xoom平板电脑的Linux / Eclipse Dev)

在我的应用程序中,我正在使用相机(startIntentForResult())来拍照。拍摄照片后,我得到了onActivityResult()回调,并且能够使用通过“拍照”意图传递的Uri加载位图。此时我的活动已恢复,尝试将图像重新加载到图库时出错:

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

我使用的唯一光标逻辑是在拍摄图像后,我使用以下逻辑将Uri转换为文件

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;

任何想法我做错了什么?

6 个答案:

答案 0 :(得分:23)

看起来在Honeycomb API中不推荐使用managedQuery()调用。

doc for managedQuery()读取:

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()**.

另外我注意到我在查询后调用了cursor.close(),我猜这是禁止的。找到了这个really helpful link。经过一番阅读后,我想出了这个似乎有效的改变。

// 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();

答案 1 :(得分:7)

为了记录,这是我在我的代码中运行的方法(在Android 1.6及更高版本上运行):我的问题是我无意中通过调用CursorAdapter.changeCursor()来关闭托管游标。在更改光标之前在适配器的光标上调用Activity.stopManagingCursor()解决了问题:

// 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);

答案 2 :(得分:5)

FIX:使用context.getContentResolver().query代替activity.managedQuery

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

答案 3 :(得分:3)

我在这里创建了这个问题,因为我无法对最后的答案发表评论(由于某种原因,评论被禁用)。我认为在这上面开一个新线程只会使事情复杂化。

当我从活动A 转到活动B 然后返回活动A 时,我的应用程序崩溃了。这不会一直发生 - 只是有时候,我很难找到确切发生的地方。所有这些都发生在同一台设备上(Nexus S),但我不认为这是设备问题。

关于@Martin Stine的回答我几乎没有问题。

  • 在文档中说明changeCursor(c);:“将基础光标更改为新光标。如果有现有光标,它将被关闭”。那么为什么我必须stopManagingCursor(currentCursor); - 不是多余的
  • 当我使用@Martin Stine提供的代码时,我得到一个空指针异常。原因是在应用程序((SimpleCursorAdapter)getListAdapter())的第一个“运行”中将评估为NULL,因为尚未创建游标。当然,我可以检查是否我没有得到null,然后尝试停止管理光标但最后我决定放置我的`stopManagingCursor(currentCursor);在此活动的onPause()方法中。我想这样我肯定会有一个光标停止管理,我应该在将Activity留给另一个之前做。问题 - 我在我的活动中使用了几个游标(一个用于填充EditText字段的文本,另一个用于列表视图)我猜不是所有游标都与ListAdapter游标相关 -
    • 我如何知道哪一个停止管理?如果我有3个不同的列表视图?
    • 我应该在onPause()期间关闭所有这些吗?
    • 如何获取所有已打开游标的列表?

这么多问题......希望有人能提供帮助。

当我到达onPause()时,我确实有一个光标停止管理,但我还没有确定这是否解决了这个问题,因为这个错误偶尔会出现。

非常感谢!


经过一些调查:

我找到了一些有趣的东西可能会给出这个问题的“神秘”方面的答案:

活动A使用两个游标:一个用于填充EditText字段。另一种是填充ListView。

从活动A移动到活动B并返回时,必须再次填写活动A中的字段+ ListView。似乎EditText字段永远不会有问题。我找不到一种方法来获取EditText字段的当前光标(就像在Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();中)并且原因告诉我EditText字段不会保留它。另一方面,ListView将从上次(从活动A - >活动B之前)“记住”其光标。另外,这很奇怪,Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();在活动B之后会有不同的ID - >活动A和所有这些没有我打电话

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

我猜在某些情况下,当系统需要释放资源时,光标将被杀死,而当活动B - >活动A,系统仍会尝试使用这个旧的死光标,这将导致异常。在其他情况下,系统将提供一个仍处于活动状态的新游标,因此不会发生异常。这可能解释了为什么有时只出现这种情况。我想这很难调试,因为运行OR调试应用程序时应用程序的速度不同。调试时,需要更多时间,因此可能会给系统时间提供新光标,反之亦然。

根据我的理解,这会使用

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

根据@Martin Stine的建议,在某些情况下必须,在其他情况下冗余:如果我返回方法并且系统正在尝试使用死光标,一个人必须创建一个新的游标并在ListAdapter中替换它,否则我会让一个崩溃的应用程序愤怒的应用程序用户。在另一种情况下,系统会发现自己的新光标 - 上面的行是多余的,因为它们使一个好的光标无效并创建一个新的光标。

我想为了防止这种冗余,我需要这样的东西:

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);

我很想听听你对此的看法!

答案 4 :(得分:2)

只需在光标块的末尾添加以下代码即可。

   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);
            } 

答案 5 :(得分:0)

这个问题困扰了我很长一段时间,我终于提出了一个简单的解决方案,就像所有Android版本的魅力一样。首先,不要使用startManagingCursor(),因为它显然是错误的并且在任何情况下都被弃用。其次,完成后尽快关闭光标。我使用try并最终确保Cursor在所有情况下都被关闭。如果你的方法必须返回一个Cursor,那么调用例程负责尽快关闭它。

我过去常常在活动的生命周期中打开游标,但我已经放弃了这种交易方法。现在我的应用程序非常稳定,并且在切换活动时不会遇到“Android错误:java.lang.IllegalStateException:尝试重新查询已经关闭的游标”,即使它们访问同一个数据库。

   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;
   }