首页 > 解决方案 > 为什么房间删除操作(使用 RxJava)即使指定不同的订阅线程也会出现 UI 线程错误?

问题描述

很简单,DAO

@Query("DELETE FROM Things WHERE someIdOfTheThing IN (:listOfId)")
abstract fun deleteThings(listOfId: MutableList<String>): Maybe<Int>

用法,

mDisposables.add(mThingsDao
    .deleteThings(listOfId)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({
         ...
     }, {
         ...
     })
)

和错误,

// Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

我想的简单想法是指定subscribeOn(Schedulers.io())然后将所有工作交给 Rx 的神奇之手,但失败了。

那么我错过了什么?


像下面这样包装并使用后deleteThingsWrapped,开始工作。但仍然不明白为什么第一种方法不起作用

open fun deleteThingsWrapped(listOfId: MutableList<String>): Maybe<Int> {
    return Maybe.create(object : MaybeOnSubscribe<Int> {
        override fun subscribe(emitter: MaybeEmitter<Int>) {
            emitter.onSuccess(deleteThings(listOfId))
        }
    })
}

@Query("DELETE FROM Things WHERE someIdOfTheThing IN (:listOfId)")
abstract fun deleteThings(listOfId: MutableList<String>): Maybe<Int>

标签: androidrx-java2android-room

解决方案


比看起来更有趣的问题<3

为了解决您的问题,我们必须查看 Room 生成的代码 - 用于以下内容:

@Transaction
@Query("DELETE FROM plants WHERE id IN (:listOfId)")
abstract fun deleteThings(listOfId: MutableList<String>): Maybe<Int>

生成的代码是:

  @Override
  public Maybe<Integer> deleteThings(final List<String> listOfId) {
    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
    _stringBuilder.append("DELETE FROM plants WHERE id IN (");
    final int _inputSize = listOfId.size();
    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
    _stringBuilder.append(")");
    final String _sql = _stringBuilder.toString();
    SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
    int _argIndex = 1;
    for (String _item : listOfId) {
      if (_item == null) {
        _stmt.bindNull(_argIndex);
      } else {
        _stmt.bindString(_argIndex, _item);
      }
      _argIndex ++;
    }
    return Maybe.fromCallable(new Callable<Integer>() {
      @Override
      public Integer call() throws Exception {
        __db.beginTransaction();
        try {
          final java.lang.Integer _result = _stmt.executeUpdateDelete();
          __db.setTransactionSuccessful();
          return _result;
        } finally {
          __db.endTransaction();
        }
      }
    });
  }

所以我们看到操作本身是在一个Maybe.fromCallable块内,这是将受到影响的部分subscribeOn(Schedulers.io())。那么如果在executeUpdateDelete();后台线程上执行,为什么会出现异常呢?


看到这一行:

SupportSQLiteStatement _stmt = __db.compileStatement(_sql);

如果我们检查这个方法的内部......

/**
 * Wrapper for {@link SupportSQLiteDatabase#compileStatement(String)}.
 *
 * @param sql The query to compile.
 * @return The compiled query.
 */
public SupportSQLiteStatement compileStatement(@NonNull String sql) {
    assertNotMainThread(); // <-- BOOM!
    return mOpenHelper.getWritableDatabase().compileStatement(sql);
}

因此,显然即使将查询放在一起,也断言不在主线程上,因此具有 aMaybe是无关紧要的;Maybe.fromCallable不管你做什么,外面的块都会在当前线程上执行。



您也可以通过在后台线程上执行“同步执行部分”来解决此问题。

mDisposables.add(
    Maybe.defer { 
        mThingsDao.deleteThings(listOfId)
                         .subscribeOn(Schedulers.io())
    }.subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({
       ...
    }, {
       ...
    })
)

推荐阅读