RxJava2 en la función de devolución de llamada onLoadFinished de CursorLoader

Para obtener datos de la base de datos uso CursorLoader en la aplicación. Una vez que el método de devolución de llamada onLoadFinished() llama a la lógica de la aplicación convierte el objeto Cursor en List de objetos dentro de los requisitos del modelo de negocio. Esa conversión (operación pesada) lleva algún tiempo si hay una gran cantidad de datos. Que se ralentiza el hilo de la interfaz de usuario. Intenté comenzar la conversión en el Thread no-UI usando RxJava2 pasa el objeto del Cursor , pero conseguido Exception :

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

Aquí está la parte del código de 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; } 

El propósito de utilizar CursorLoader aquí es obtener notificaciones de DB si hay un almacén de datos actualizado.

Actualización Como sugerido Tin Tran, he eliminado CursorUtil.closeSafely(cursor); Y ahora tengo otra excepción:

 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() método de 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; } 

Como puedes ver en mi comentario a tu pregunta, me interesó saber si los datos se están actualizando mientras getFieldsObservable() no ha sido devuelto. Recibí la información que me interesó en su comentario .

Como puedo juzgar, esto es lo que sucede en su caso:

  • onLoadFinished() se llama con Cursor-1
  • El método de RxJava se está ejecutando en otro hilo con Cursor-1 (todavía no se ha terminado, aquí se está usando Cursor-1)
  • onLoadFinished() se llama con Cursor-2, LoaderManager API se encarga de cerrar Cursor-1 , que todavía está siendo consultado por RxJava en otro hilo

Por lo tanto, se produce una excepción.

Por lo tanto, es mejor seguir con la creación de su personalizado AsyncTaskLoader (que CursorLoader extiende desde). Este AsyncTaskLoader incorporará todas las lógicas que CursorLoader tiene (básicamente una copia uno a uno), pero devolverá el objeto que ya está ordenado / filtrado en onLoadFinished(YourCustomObject) . Por lo tanto, la operación, que usted desea realizar utilizando RxJava realmente sería hecho por su cargador en su método loadInBackground() .

Esta es la instantánea de los cambios que MyCustomLoader tendrá en el método 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 { ... } } ... } 

Donde PojoWrapper es:

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

Por lo tanto, en onLoadFinished() no tiene que encargarse de delegar el trabajo a otro hilo, ya que ya lo ha hecho en la implementación de Loader :

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

Aquí está el código completo de MyCustomLoader .

El cargador liberará los datos una vez que sepa que la aplicación ya no lo está utilizando. Por ejemplo, si los datos son un cursor de un CursorLoader, no debe llamar a close () en él mismo. De: https://developer.android.com/guide/components/loaders.html

Usted no debe cerrar el cursor usted mismo que creo CursorUtil.closeSafely(cursor) hace.

Puede utilizar el operador switchMap para implementarlo. Hace exactamente lo que queremos

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

Ahora tiene que modificar showFields y getFieldsObservable para tener en cuenta el Cursor vacío

  • ¿Qué diferencia hay entre SQLite y SQL?
  • ¿Dónde se almacena la base de datos SQLite en el emulador de Android?
  • Versión de SQLite utilizada en Android?
  • ¿Cuál es la ventaja de usar SQLite en lugar de File?
  • SQlite Obteniendo las ubicaciones más cercanas (con latitud y longitud)
  • Conjunto de conexiones SQLite
  • La aplicación se bloquea al iniciar Debido a java.lang.IllegalArgumentException: la columna '_id' no existe
  • Cómo obtener el valor de la columna Timestamp en android mediante cursor
  • Android SQLite ORDER BY no funciona
  • Selección de un proyecto ORM para Android (nivel mínimo de API 7)
  • Android - BaseColumns y _id
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.