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
:
- Establecer un valor predeterminado en una columna en SQLite
- ¿La reinstalación de la aplicación suprime SQLiteDatabase o SharedPreferences?
- SQLiteDatabase close () función que causa NullPointerException cuando varios subprocesos
- ¿Cómo puedo obtener información sobre la columna causó restricción de clave externa en android sqlite?
- Cómo reemplazar el archivo de base de datos sqlite existente con un nuevo archivo de base de datos en android
@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; }
- Android: Cómo cargar imagen dinámicamente desde el servidor por su nombre de SQlite
- Android: carga el contenido de la base de datos sqlite en webview
- Utilizar el operador OR con 2 marcadores de posición con el mismo valor
- ¿Debo habilitar la restricción de clave externa en onOpen o onConfigure?
- Cómo almacenar datos desde un archivo XML a una base de datos SQLite en android
- Función de actualización en android SQLite no funciona
- Almacenamiento y recuperación de datos no ingleses en la base de datos sqlite en android
- No se puede usar como cláusula en la aplicación android
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