Ошибка Android: java.ленг.IllegalStateException: попытка запросить уже закрытый курсор

окружающая среда (Linux/Eclipse Dev для планшета Xoom под управлением 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;

любые идеи, что я делаю неправильно?

6 ответов


похоже, что вызов managedQuery () устарел в API Honeycomb.

Doc для 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()**.

также я заметил, что вызываю курсор.close () после запроса, который, я думаю, является no-no. Нашел это очень полезная ссылка как хорошо. После некоторого чтения я придумал это изменение, которое, кажется, работает.

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

для записи, вот как я исправил это в своем коде (который работает на Android 1.6 и выше): проблема в моем случае заключалась в том, что я непреднамеренно закрывал управляемые курсоры, вызывая CursorAdapter.changeCursor(). Вызываю Активность.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);

исправить: используйте 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;

я создал этот вопрос здесь, поскольку я не мог прокомментировать последний ответ (комментарии отключены по какой-то причине). Я думал, что открытие новой темы только усложнит ситуацию.

я получаю сбои приложений, когда я иду от Действие A to Действие B а затем вернуться к Действие A. Это не происходит все время - только иногда и мне трудно найти, где именно это происходит. Все происходит на то же устройство (Nexus S), но я не считаю, что это проблема устройства.

у меня есть несколько вопросов относительно ответа @Martin Stine.

  • в документации говорится о changeCursor(c);: "измените базовый курсор на новый курсор. Если есть существующий курсор, он будет закрыт". Так почему же я должен stopManagingCursor(currentCursor); - это не перебор?
  • когда я использую код, предлагаемый @Martin Stine, я получаю исключение нулевого указателя. Причина то есть, что в первом "запуске" приложения ((SimpleCursorAdapter)getListAdapter()) будет оцениваться как NULL, потому что курсор еще не был создан. Конечно, я мог бы проверить, не получаю ли я null и только затем попытаться остановить управление курсором, но, наконец, я решил поместить свой " stopManagingCursor(currentCursor); в метод onPause() этого действия. Я думал, что таким образом у меня наверняка будет курсор, чтобы перестать управлять, и я должен сделать это, прежде чем я оставлю действие другому. Проблема - я использую несколько курсоры (один для заполнения текста поля EditText, а другой для представления списка) в моей деятельности, я думаю, не все из них связаны с курсором ListAdapter -
    • как узнать, какой из них перестать управлять? Если у меня есть 3 разных списка?
    • должен ли я закрыть все из них во время onPause()?
    • как получить список всех моих открытых курсоров?

так много вопросов... Надеюсь, кто-нибудь сможет помощь.

когда я доберусь до onPause() у меня есть курсор, чтобы остановить управление, но я еще не определил, решает ли это проблему, поскольку эта ошибка появляется спорадически.

большое спасибо!


ПОСЛЕ НЕКОТОРОГО РАССЛЕДОВАНИЯ:

я нашел кое-что интересное, что может дать ответ на "таинственную" сторону этого вопроса:

Activity A использует два курсора: один для заполнения поля EditText. Другой - заполнить ListView.

при переходе от действия A к действию B и возвращении поле + ListView в действии A должно быть заполнено снова. Похоже, что поле EditText никогда не будет иметь проблем с этим. Я не мог найти способ получить текущий курсор поля EditText (например, в Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();) и причина говорит мне, что поле EditText не будет его хранить. С другой стороны, ListView "запомнит" свой курсор с прошлого раза (до действия A -> действие B). Кроме того, и это странная вещь,Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); будет иметь другой идентификатор после действия B - > Activity A и все это без меня всегда зовут

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

Я думаю, что в некоторых случаях, когда системе нужно освободить ресурсы, курсор будет убит, а когда действие B -> действие A, система все равно попытается использовать этот старый мертвый курсор, что приведет к исключению. И в других случаях система придумает новый курсор, который все еще жив, и, таким образом, никаких исключений не произойдет. Это может объяснить, почему это появляется только иногда. Я думаю, это трудно отладить из-за разницы в скорости приложения при запуске или отладке приложения. При отладке требуется больше времени и, таким образом, может дать системе время придумать новый курсор или наоборот.

в моем понимании это делает использование

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

как рекомендовано @Martin Stine a должны в некоторых случаях и резервные in Другие: если я возвращаюсь к методу, и система пытается использовать мертвый курсор, необходимо создать новый курсор и заменить его в 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);

Я хотел бы услышать, что подумай об этом!


просто добавьте следующий код в конец блока курсора..

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

эта проблема беспокоит меня уже давно и я, наконец, придумал простое решение, которое работает как шарм на всех версиях Android. Во-первых, не используйте startManagingCursor (), так как он, очевидно, глючит и устарел в любом случае. Во-вторых, закройте курсор как можно скорее после того, как вы закончите с ним. Я использую try и, наконец, чтобы убедиться, что курсор закрывается при любых обстоятельствах. Если ваш метод должен вернуть курсор, то вызывающая процедура отвечает за его закрытие как как можно скорее.

раньше я оставлял курсоры открытыми на всю жизнь деятельности, но с тех пор я отказался от этого для этого транзакционного подхода. Теперь мое приложение очень стабильно и не страдает от "Android error: java.ленг.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;
   }