Слияние курсоров во время onLoadFinished() вызывает StaleDataException после поворота

Я загружаю некоторые результаты из базы данных, используя loaderManager. К сожалению, следующий код создает исключение StaleDataException после поворота устройства:

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor)
{
    // If we've returned results, then pass them and the webSearches cursor along to be displayed
    if(cursor.moveToFirst())
    {
        // Get a cursor containing additional web searches and merge it at the end of the results cursor 
        MatrixCursor searchesCursor = getWebSearchesCursor(searchTerm, false);
        Cursor[] cursors = { cursor, searchesCursor };
        // TODO: Figure out why merging cursors here causes staledataexception after rotation
        Cursor results = new MergeCursor(cursors);
        // Display the cursor in the ListView
        adapter.changeCursor(results);
    }
    // If no results were returned, then return suggestions for web searches
    else
    {
        // Get a cursor containing additional web searches 
        MatrixCursor noResults = getWebSearchesCursor(searchTerm, true);
        adapter.changeCursor(noResults);    
    }

    // Show the listView and hide the progress spinner
    toggleListView(SHOW);
}

Вызов getWebSearchesCursor() возвращает MatrixCursor с некоторыми дополнительными запросами поиска, сопровождающими любые возвращаемые результаты. Я обнаружил, что изменение adaptor.changeCursor (результаты) на adapter.changeCursor (курсор) исправляет ошибку, поэтому похоже, что слияние MatrixCursor с возвращенным курсором приводит к ошибке.

У меня вопрос, почему?

Если будут возвращены какие-либо результаты, я бы хотел добавить дополнительные элементы к возвращаемому курсору, чтобы у пользователя была возможность выполнить поиск на нескольких веб-сайтах. Есть ли лучший способ объединить курсоры, чтобы я не получил это исключение после поворота?

2 ответа

Решение

Эта проблема снова возникла пару дней назад, и мне посчастливилось наткнуться на решение.

Я обнаружил, что я должен был использовать swapCursor() вместо changeCursor(). Согласно документации для Android:

Поменяйте местами новый Курсор, вернув старый Курсор. В отличие от changeCursor(Cursor), возвращенный старый Cursor не закрывается.

...

Если данный новый Курсор является тем же экземпляром, что и ранее установленный Курсор, также возвращается ноль.

Эта последняя часть, казалось, была ключевой. Ошибка, упомянутая в приведенном выше вопросе, может быть прослежена до удушения CursorAdapter на объединенном курсоре, потому что он был закрыт, когда пытался перерисовать фрагмент после поворота. Вместо этого, используя swapCursor(), CursorAdapter смог повторно использовать "старый" объединенный курсор вместо того, чтобы ставить под сомнение его валидность и выдавать исключение StaleDataException.

Я делаю некоторые предположения здесь; возможно, кто-то, кто более разбирается во внутренней работе Android, сможет подтвердить или опровергнуть мои рассуждения.

Если вы начали использовать swapCursor() вместо changeCursor() везде, тогда я надеюсь, что вы также начали обрабатывать закрытие курсора в этих местах.

changeCursor() закроет старый курсор, это преднамеренно и работает безупречно, когда вы просто используете курсор, предоставленный onLoadFinished(), Это сделано таким образом, чтобы вам не пришлось беспокоиться о его закрытии.

Когда вы поворачиваете свое устройство, система Android проверит, что курсор, который вы послали вам в прошлый раз, еще не был закрыт, и отправит его снова, а не тратит ресурсы на создание нового. Ваш код оборачивает этот курсор в новый экземпляр MergeCursor который передается changeCursor(), который видит, что это не тот объект, который был получен ранее, и решает закрыть старый экземпляр. поскольку MergeCursor только обводит курсоры, которые вы передаете, вместо того, чтобы копировать данные в них, ваш новый экземпляр теперь содержит (как минимум) один закрытый курсор.

Чтобы правильно справиться с этим, вам нужно написать собственный код, который проверяет, прошел ли курсор onLoadFinished() такой же, как у вас в вашем текущем MergeCursor экземпляр, и закройте существующий экземпляр, только если вы получаете новый курсор. Конечно, вам также нужно отслеживать другие курсоры, завернутые в тот же MergeCursor пример.

Другие вопросы по тегам