Intereting Posts
Ошибка API YouTube Youtube YouTubePlayerView Android: как проверить, запущена ли работа? EditText Settext не работает с фрагментом OnInfoListener никогда не называется Ориентация видеомагнитофона JavaCV не подходит в портретном режиме Как вернуть результат посредством нескольких действий Получить координаты из аппаратного GPS Android – выделить текст в текстовом поле? Пороговое изображение с использованием opencv (Java) Как центрировать заголовок на панели инструментов (даже при отображении кнопки панели инструментов) Android: Кто-нибудь знает, как захватить видео? Является ли ArrayAdapter потоком безопасным в android? Если нет, что я могу сделать, чтобы сделать его потокобезопасным? Android – Откройте браузер с URL-адресом с заголовками запросов Слайд RecyclerView вверх по мере того, как вы прокручиваете его или пересказываете recyclerview вниз при прокрутке вниз Лучший способ справиться с обратным поведением на DialogFragment?

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