2011-05-06 200 views
17

环境(Linux的/ Eclipse的开发对Xoom的平板电脑上运行蜂窝3.0.1)Android的错误:java.lang.IllegalStateException:尝试重新查询一个已经关闭的游标

在我的应用我使用的相机(startIntentForResult( )) 拍照。拍完照片后,我得到了onActivityResult()回调,并能够使用通过“拍照”意图传递的Uri加载Bitmap。在这一点上我的活动重新开始,我在尝试将图像加载到图片库的错误:

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; 

任何想法我做错了什么?

回答

22

看起来像在Honeycomb API中弃用了managedQuery()调用。

文件的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(); 
+0

你是专门针对蜂窝。刚刚发现,由于startManagingCursor(),我的Eclair +应用程序在Honeycomb中被破解。我的应用程序针对的是手机,但是当冰淇淋三明治出现时,并不期待愤怒的客户。 – Tenfour04 2011-05-13 22:05:47

+0

是的,我专门针对蜂窝(片剂)。 – 2011-05-18 20:58:16

+0

@ cyber-monk嗨 - 我有问题。我无法实现这个,因为我的代码无法识别CursorLoader。我正确导入它,但它不能识别导入。我该如何解决? – Mxyk 2012-03-02 13:51:05

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; 
+0

我正面临Android操作系统版本3.0及以上的类似问题,我针对智能手机和平板电脑的应用程序。我尝试了上述建议的解决方案,他们似乎并没有解决这个问题。如果每个人都有解决此问题的其他方法,请发布。 – Rise 2011-11-17 12:44:43

+0

@Rupesh。我想你现在已经解决了你的问题,但是既然你问了,我已经在一个新的答案中发布了我的解决方案。希望其他人会从中受益。 – 2012-02-04 11:17:16

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

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

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

关于@Martin Stine的回答,我有几个问题。

  • 在关于changeCursor(c);的文档中提到:“将底层游标更改为新的游标,如果存在已存在的游标,它将被关闭”。那么为什么我要stopManagingCursor(currentCursor); - 是不是多余的
  • 当我使用@Martin Stine提供的代码时,我得到一个空指针异常。原因是在应用程序((SimpleCursorAdapter)getListAdapter())的第一个“运行”中将评估为NULL,因为尚未创建游标。当然,我可以检查是否我没有得到一个null,然后尝试停止管理光标,但最后我决定放置我的`stopManagingCursor(currentCursor);在此活动的onPause()方法中。我认为这样我肯定会有一个光标停止管理,我应该在我离开活动到另一个之前做。问题 - 我在我的活动中使用了几个游标(一个填充了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而这一切WITHOUT我曾经呼吁

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

我想在某些情况下,系统需求时,为了释放资源,游标将被杀死,当Activity B - > Activity A时,系统仍会尝试使用这个旧的死游标,这将导致异常。而在其他情况下,系统会产生一个仍然存在的新游标,因此不会发生异常。这也许可以解释为什么有时只显示这些。我想这是很难调试,因为在运行或调试应用程序时,由于应用程序速度的差异。调试时,需要更多时间,因此可能会让系统有时间想出新的光标,反之亦然。

在我的理解,这使得的

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

使用所推荐的@马丁斯坦一个必须在某些情况下,而在其他冗余:如果我回到方法和系统试图要使用死光标,必须创建一个新的光标并将其替换到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); 

我很想听听你想想这个!

0

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

我曾经在一个Activity的生命周期中让游标保持打开状态,但是我已经放弃了这个事务性方法。现在我的应用程序非常稳定,并且在切换活动时即使访问相同的数据库也不会遇到“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; 
    } 
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); 
      } 
+1

stopManagingCursor(c);最后为我工作!简单! :) – 2014-04-20 16:42:39

相关问题