18

环境(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;

任何想法我做错了什么?

4

6 回答 6

23

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();
于 2011-05-06T18:40:54.830 回答
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);
于 2012-02-04T11:28:54.023 回答
5

修复:使用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;
于 2011-09-19T10:17:24.107 回答
3

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

当我从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 字段当前光标的方法(例如 in 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 时,系统仍然会尝试使用这个旧的死游标,这会导致异常。而在其他情况下,系统会提出一个新的游标,它仍然活着,因此不会发生异常。这也许可以解释为什么这只是有时出现。我想由于运行或调试应用程序时应用程序速度的差异,这很难调试。调试时,它需要更多时间,因此可能会给系统时间来产生新的光标,反之亦然。

据我了解,这使得使用

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

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

于 2012-06-01T07:01:42.003 回答
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);
            } 
于 2013-07-09T12:16:02.117 回答
0

这个问题困扰了我很长时间,我终于想出了一个简单的解决方案,它在所有版本的 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;
   }
于 2012-10-23T15:15:22.820 回答