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

#android #illegalstateexception #android-cursor #android-loadermanager

#Android #исключение illegalstateexception #android-cursor #android-loadermanager

Вопрос:

среда (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;
  

Есть идеи, что я делаю не так?

Ответ №1:

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

Документ для 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() после запроса, который, как я предполагаю, является 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();
  

Комментарии:

1. Вы специально ориентируетесь на Honeycomb. Только что обнаружил, что мое приложение Eclair сломано в Honeycomb из-за startManagingCursor (). Мое приложение предназначено для телефонов, но я не жду разгневанных клиентов, когда выйдет Ice Cream Sandwich.

2. Да, я специально ориентируюсь на Honeycomb (планшеты).

3. @cyber-monk Привет — у меня проблема. Я не могу реализовать это, потому что мой код не распознает CursorLoader. Я импортировал его правильно, но он также не распознает импорт. Как мне это исправить?

4. @MikeGates Похоже, что из приведенной информации трудно ответить на проблемы с конфигурацией проекта, я бы дважды проверил API, указанный для вашего проекта.

5. Лучше избавьтесь от 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;
  

Комментарии:

1. Я сталкиваюсь с аналогичной проблемой в ОС Android версии 3.0 и выше, я ориентируюсь на приложение для смартфонов и планшетов. Я попробовал предложенные выше решения, они, похоже, не решили эту проблему. Если у всех есть какой-либо альтернативный подход для решения этой проблемы, пожалуйста, опубликуйте его.

2. @Rupesh. Я предполагаю, что вы уже решили свою проблему, но поскольку вы спросили, я опубликовал свое решение в новом ответе. Надеюсь, другие извлекут из этого выгоду.

Ответ №4:

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

Я получаю сбой приложения, когда я перехожу от действия 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 и возврате поле ListView в действии A должно быть заполнено снова. Похоже, что у поля EditText никогда не возникнет проблем с этим. Я не смог найти способ получить текущий курсор в поле EditText (как в Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); ), и причина подсказывает мне, что поле EditText не сохранит его. С другой стороны, ListView «запомнит» свой курсор с прошлого раза (с момента до действия A -> Действие B). Кроме того, и это самое странное, у Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor(); будет другой идентификатор после действия B -> Действия A, и все это БЕЗ моего вызова

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

Я предполагаю, что в некоторых случаях, когда системе необходимо освободить ресурсы, курсор будет уничтожен, и когда действие B -> Действие 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);
  

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

Ответ №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, чтобы гарантировать, что курсор будет закрыт при любых обстоятельствах. Если ваш метод должен вернуть курсор, то вызывающая процедура отвечает за его закрытие как можно скорее.

Раньше я оставлял курсоры открытыми на время действия, но с тех пор отказался от этого для этого транзакционного подхода. Теперь мое приложение очень стабильно и не страдает от «Ошибки Android: java.lang.Исключение IllegalStateException: попытка запросить уже закрытый курсор » при переключении действий, даже если они обращаются к одной и той же базе данных.

    static public Boolean musicReferencedByOtherFlash(NotesDB db, long rowIdImage)
   {
      Cursor dt = null;
      try
      {
         dt = db.getNotesWithMusic(rowIdImage);
         if (   (dt != null)
             amp;amp; (dt.getCount() > 1))
            return true;
      }
      finally
      {
         if (dt != null)
            dt.close();
      }
      return false;
   }