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
:
- Cómo encontrar y borrar el archivo db de SQLite en Android (emulador)
- Cómo almacenar el objeto JSON en la base de datos SQLite
- Android SQLite COMO comodín de escape
- Android: Guardando valores booleanos en una base de datos (Principiante)
- NoClassDefFoundError al usar greenDao
@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; }
- ¿Qué caracteres no se pueden utilizar para valores en bases de datos SQLite?
- ActiveAndroid Relación de muchos a muchos
- Compruebe si la consulta SQL ha tenido éxito en Android SQLite
- Android SqliteAssetHelper - fusionar las tablas de la base de datos del activo con el existente
- Almacenamiento de datos de Android - Archivo vs SQLite
- Android sqlite bloqueo con el sistema de archivos exFAT
- Devuelve el recuento de registros en una variable, Sqlite, Android, Java
- El mejor lugar para cerrar la conexión de la base de datos
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 cerrarCursor-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
- Android 5.0 Datepicker en scrollview no funciona (no se desplaza meses)
- Cómo eliminar el marco del dispositivo en los emuladores de Android Studio