【问题标题】:RxJava2 in CursorLoader’s onLoadFinished callbackCursorLoader 的 onLoadFinished 回调中的 RxJava2
【发布时间】:2017-06-06 12:29:28
【问题描述】:

为了从数据库中获取数据,我在应用程序中使用了CursorLoader。一旦onLoadFinished()回调方法调用应用程序的逻辑将Cursor对象转换为业务模型要求内的List对象。如果有大量数据,这种转换(繁重的操作)需要一些时间。这会减慢 UI 线程。我尝试使用RxJava2 传递Cursor 对象在非UI Thread 中开始转换,但得到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;
    }

【问题讨论】:

  • 不是答案,但我强烈建议您查看sqlbrite
  • @Lukasz,是的,我知道 sqlbrite,这将是一个很好的解决方案,但我无法将它添加到项目中,因为它已经很大并且使用了另一个 orm。
  • 可以在处理之前的数据时更新此列表吗?

标签: android sqlite rx-java rx-android android-cursorloader


【解决方案1】:

正如您从my comment 看到的问题,我很感兴趣在getFieldsObservable() 尚未返回时数据是否正在更新。我收到了我感兴趣的信息in your comment

据我判断,您的情况如下:

  • onLoadFinished() 使用 Cursor-1 调用
  • RxJava 的方法正在使用 Cursor-1 的另一个线程上执行(尚未完成,这里正在使用 Cursor-1)
  • onLoadFinished() 使用 Cursor-2 调用,LoaderManager API 负责关闭 Cursor-1,RxJava 仍在另一个线程上查询它

因此,出现异常。

因此,您最好坚持创建您的自定义 AsyncTaskLoaderCursorLoader 扩展自)。这个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'sMyCustomLoader的整个代码。

【讨论】:

    【解决方案2】:

    一旦知道应用程序不再使用数据,加载程序就会释放数据。例如,如果数据是来自 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

    【讨论】:

    • 是的,它确实关闭了Cursor。但是 OP 在他不再需要它时将其关闭,这意味着他已经从 Cursor“解析” farmList,并且他不再需要打开 Cursor。我认为这里没有问题。
    • 是的,但是在他关闭Cursor之后,CursorLoader可能仍然需要访问游标。如果是,则抛出Exception
    • 我明白你的意思。我想知道LoaderManager 是否不够聪明,无法在Cursor 关闭时执行if 检查并再次加载。
    • 我已经按照 Tin Tran 的建议在没有关闭光标的情况下进行了测试,并得到了另一个异常,添加了跟踪作为问题的更新。
    • 问题是Cursor 不是线程安全的。当您将光标转换为List&lt;Field&gt; 时,新的Cursor 到达onLoadFinished 并且旧的光标接近。因此,访问它的io线程会抛出异常。
    猜你喜欢
    • 1970-01-01
    • 2016-08-19
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    • 2013-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多