要从数据库中获取数据,我在应用中使用CursorLoader
。一旦onLoadFinished()
回调方法调用应用程序的逻辑,就会将Cursor
对象转换为业务模型要求中对象的List
。如果有大量数据,那么转换(繁重操作)需要一些时间。这会降低UI线程的速度。我尝试使用Thread
传递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
的目的是在数据存储更新时从DB获取通知。
更新
正如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;
}
答案 0 :(得分:6)
从my comment到您的问题可以看到,我感兴趣的是在尚未返回getFieldsObservable()
时是否正在更新数据。我收到了我感兴趣的信息in your comment。
我可以判断,这是你的情况:
onLoadFinished()
onLoadFinished()
,LoaderManager API负责关闭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;
}
Here's MyCustomLoader
的完整代码。
答案 1 :(得分:4)
一旦知道应用程序不再使用它,加载程序就会释放数据。例如,如果数据是来自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