Intereting Posts
Загрузка нескольких изображений с помощью волейбола? Какой размер лучше всего использовать для значка приложения для Android Как изменить стиль по умолчанию EditText Android setOnCheckedChangeListener снова звонит, когда возвращается старый вид Вы можете запустить собственное приложение для камеры из веб-приложения Html 5? NoClassDefFoundError в библиотеке V2 для Google Play Services Как отключить «автономный режим» gradle в студии Android? Отсутствующие стили. Правильная ли тема выбрана для этого макета? Как создать прядильщик Android без треугольника в правой части виджета Подключение к wi-fi с помощью оболочки adb Как получить текст, введенный при поиске в строке действий, в строку? Тяжесть TextView не работает с ивритом Android – соответствует шаблону url? Используйте ItemTouchHelper для Swipe-To-Dismiss с другим представлением, отображаемым за выведенным Как сбросить фоновый цвет кнопки по умолчанию?

RxJava2 в CursorLoader's onLoadFinished callback

Для получения данных из базы данных я использую CursorLoader в приложении. Когда onLoadFinished() callback onLoadFinished() вызывает логику приложения, он преобразует объект Cursor в List объектов в рамках требований бизнес-модели. Это преобразование (тяжелая операция) занимает некоторое время, если имеется много данных. Это замедляет поток пользовательского интерфейса. Я попытался начать преобразование в не-UI- RxJava2 используя RxJava2 проходящий объект Cursor , но получил Exception :

 Caused by: android.database.StaleDataException: Attempting to access a closed CursorWindow.Most probable cause: cursor is deactivated prior to calling this method. 

Вот часть кода Fragment :

 @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { QueryBuilder builder; switch (id) { case Constants.FIELDS_QUERY_TOKEN: builder = QueryBuilderFacade.getFieldsQB(activity); return new QueryCursorLoader(activity, builder); default: return null; } } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { if (cursor.getCount() > 0) { getFieldsObservable(cursor) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::showFields); } else { showNoData(); } } private static Observable<List<Field>> getFieldsObservable(Cursor cursor) { return Observable.defer(() -> Observable.just(getFields(cursor))); <-- Exception raised at this line } private static List<Field> getFields(Cursor cursor) { List<Field> farmList = CursorUtil.cursorToList(cursor, Field.class); CursorUtil.closeSafely(cursor); return farmList; } 

Цель использования CursorLoader здесь – получить уведомления от БД, если обновление данных обновлено.

Обновление Как предложил Tin Tran, я удалил CursorUtil.closeSafely(cursor); И теперь я получаю другое исключение:

 Caused by: java.lang.IllegalStateException: attempt to re-open an already-closed object: /data/user/0/com.my.project/databases/db_file at android.database.sqlite.SQLiteClosable.acquireReference(SQLiteClosable.java:55) at android.database.CursorWindow.getNumRows(CursorWindow.java:225) at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:121) at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:236) at android.database.AbstractCursor.moveToNext(AbstractCursor.java:274) at android.database.CursorWrapper.moveToNext(CursorWrapper.java:202) at com.db.util.CursorUtil.cursorToList(CursorUtil.java:44) at com.my.project.MyFragment.getFields(MyFragment.java:230) 

cursorToList() метод CursorUtil

 public static <T> ArrayList<T> cursorToList(Cursor cursor, Class<T> modelClass) { ArrayList<T> items = new ArrayList<T>(); if (!isCursorEmpty(cursor)) { while (cursor.moveToNext()) { <-- at this line (44) of the method raised that issue final T model = buildModel(modelClass, cursor); items.add(model); } } return items; } 

Solutions Collecting From Web of "RxJava2 в CursorLoader's onLoadFinished callback"

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

Как я могу судить, вот что происходит в вашем случае:

  • onLoadFinished() вызывается с помощью курсора-1
  • Метод RxJava выполняется в другом потоке с помощью Cursor-1 (еще не закончен, здесь используется Cursor-1)
  • onLoadFinished() вызывается с Cursor-2, API LoaderManager заботится о закрытии Cursor-1 , который по-прежнему запрашивается RxJava в другом потоке

Таким образом, получается исключение.

Таким образом, вам лучше придерживаться создания пользовательского AsyncTaskLoader (из которого CursorLoader продолжается). Этот AsyncTaskLoader будет включать в себя все логики, которые CursorLoader имеет (в основном, один-к-одному), но возвратит уже отсортированный / фильтрующий объект в onLoadFinished(YourCustomObject) . Таким образом, операция, которую вы хотели бы использовать с помощью RxJava, была бы фактически выполнена вашим загрузчиком в его loadInBackground() .

Вот моментальный снимок изменений, которые MyCustomLoader будет иметь в loadInBackground() :

 public class MyCustomLoader extends AsyncTaskLoader<PojoWrapper> { ... /* Runs on a worker thread */ @Override public PojoWrapper loadInBackground() { ... try { Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder, mCancellationSignal); ... // `CursorLoader` performs following: // return cursor; // We perform some operation here with `cursor` // and return PojoWrapper, that consists of `cursor` and `List<Pojo>` List<Pojo> list = CursorUtil.cursorToList(cursor, Field.class); return new PojoWrapper(cursor, list); } finally { ... } } ... } 

Где PojoWrapper :

 public class PojoWrapper { Cursor cursor; List<Pojo> list; public PojoWrapper(Cursor cursor, List<Pojo> list) { this.cursor = cursor; this.list = list; } } 

Таким образом, в onLoadFinished() вам не нужно заботиться о делегировании задания другому потоку, потому что вы уже сделали это в своей реализации Loader :

 @Override public void onLoadFinished(Loader<PojoWrapper> loader, PojoWrapper data) { List<Pojo> alreadySortedList = data.list; } 

Вот весь код MyCustomLoader .

Загрузчик выдает данные, как только он узнает, что приложение больше не использует его. Например, если данные являются курсором из CursorLoader, вы не должны называть его close () самостоятельно. От: https://developer.android.com/guide/components/loaders.html

Вы не должны закрывать курсор самостоятельно, что я считаю CursorUtil.closeSafely(cursor) .

Вы можете использовать оператор switchMap для его реализации. Он делает именно то, что мы хотим

 private PublishSubject<Cursor> cursorSubject = PublishSubject.create() public void onCreate(Bundle savedInstanceState) { cursorSubject .switchMap(new Func1<Cursor, Observable<List<Field>>>() { @Override public Observable<List<Field>> call(Cursor cursor) { return getFieldsObservable(cursor); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::showFields); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { cursorSubject.onNext(cursor) } 

Теперь вам нужно изменить showFields и getFieldsObservable для учета пустого Cursor