【问题标题】:Android error: java.lang.IllegalStateException: trying to requery an already closed cursorAndroid 错误:java.lang.IllegalStateException:试图重新查询已经关闭的游标
【发布时间】:2011-08-20 09:52:33
【问题描述】:

环境(Linux/Eclipse Dev for Xoom Tablet 运行 HoneyComb 3.0.1)

在我的应用程序中,我使用相机 (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;

任何想法我做错了什么?

【问题讨论】:

    标签: android illegalstateexception android-cursor android-loadermanager


    【解决方案1】:

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

    【讨论】:

    • 您是否专门针对 Honeycomb。刚刚发现我的 Eclair+ 应用程序在 Honeycomb 中被破坏,因为 startManagingCursor()。我的应用是针对手机的,但是当冰淇淋三明治问世时,我并不期待愤怒的顾客。
    • 是的,我专门针对 Honeycomb(平板电脑)。
    • @cyber-monk 嗨 - 我有问题。我无法实现这一点,因为我的代码无法识别 CursorLoader。我正确导入了它,但它也无法识别导入。我该如何解决这个问题?
    • @MikeGates 听起来项目配置问题很难从给定的信息中回答,我会仔细检查为您的项目指定的 API。
    • 最好摆脱 startManagingCursor,在你的活动中创建 mCursor。在 onPause 或 onDestroy 中关闭它(取决于您是在 onResume 还是 onCreate 中填充它)。
    【解决方案2】:

    作为记录,这是我在我的代码(在 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】:

      修复:使用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;
      

      【讨论】:

      • 我在 android OS 3.0 及更高版本上遇到了类似的问题,我的目标是智能手机和平板电脑的应用程序。我尝试了上述建议的解决方案,他们似乎没有解决这个问题问题。如果每个人都有解决此问题的替代方法,请发布。
      • @Rupesh。我想你现在已经解决了你的问题,但是既然你问了,我已经在一个新的答案中发布了我的解决方案。希望其他人能从中受益。
      【解决方案4】:

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

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

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

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

      这么多问题...希望任何人都可以提供帮助。

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

      非常感谢!


      经过一番调查:

      我发现了一些有趣的东西,可能会回答这个问题的“神秘”方面:

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

      从Activity A移动到Activity B再回来时,Activity A中的field + ListView必须重新填满。似乎 EditText 字段永远不会有问题。我找不到获取 EditText 字段当前光标的方法(例如在 Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); 中),原因告诉我 EditText 字段不会保留它。另一方面,ListView 将“记住”它上次的光标(从 Activity A -> Activity B 之前)。另外,这很奇怪,Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); 在 Activity B -> Activity A 之后会有一个不同的 ID,而这一切没有我打电话

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

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

      据我了解,这使得使用

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

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

      【讨论】:

        【解决方案5】:

        只需在光标块的末尾添加以下代码..

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

        【讨论】:

          【解决方案6】:

          这个问题困扰了我很长时间,我终于想出了一个简单的解决方案,它在所有版本的 Android 上都像一个魅力。首先,不要使用 startManagingCursor() 因为它显然是错误的并且在任何情况下都不推荐使用。其次,在完成后尽快关闭光标。我使用 try 和 finally 来确保 Cursor 在所有情况下都关闭。如果您的方法必须返回一个 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;
             }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-08-28
            • 1970-01-01
            • 1970-01-01
            • 2013-04-18
            • 2023-03-08
            • 1970-01-01
            • 1970-01-01
            • 2015-03-06
            相关资源
            最近更新 更多