文章目录
以下内容基于
Room 2.1.0-alpha04
ObjectBox升级到2.3后,与正在使用中的百度的“离在线语音识别”SDK产生了严重的so native冲突会导致崩溃,这个问题实在是有心无力,正好趁此机会,换用谷爹强推的Room持久库。
本篇记录了Room配合RxJava2使用的方式及一些注意事项,从数据库操作而非Room注解的角度来总结一下Room的操作方式,其中部分信息来源都来自于Room&RxJava和Room的注释文档。
为什么Room一定要配合RxJava来使用
当然是因为用起来很爽啦
其实由于Room是不能在主线程进行数据库操作的,一在主线程操作,系统就会用java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
来教你做一名“Android Developer”,但是Room本身又没有提供切换线程的简便方法,最多也就配合LiveData
可以做到自动在IO线程操作。但实际上用LiveData
专门来做这种事情简直是杀鸡用牛刀,LiveData
配合Room使用,本意是得到一个可观测的对象,并且这个对象会随着数据库中数据的变化不停的接收到变化后的数据,从来不停的更新UI,使UI始终展示的是数据库中的最新数据。然而根据我有限的Android开发经验来看,这种的应用场景简直少之又少,至少我是从来没遇见过的。
基于目前Android使用数据库进行“增删改查”,基本上都是一次性操作,很少有需要持续观察数据库变化的情况,所以,目前看来,Room配合RxJava,最大的好处就是不用写一堆线程切换的玩意儿了。
基本使用方法
返回的查询结果对象
INSERT,UPDATE,DELETE操作可以返回Completable
,Single
和Maybe
三种对象,而QEURY操作,如果是一次性查询,可以返回Single
,Maybe
,如需要可观测对象,可以返回Observable
和Flowable
对象,但是注意,不能返回Completable
对象。
这里有一点需要注意,是什么操作取决于具体做的是什么操作,而不是注解。听起来可能有点绕,举个栗子就明白了:
@QUERY(DELETE ***)
也属于DELETE操作。
先来简单介绍一下这几个属于RxJava的对象的特点。
Completable
:只有onComplete
和onError
方法,即是只有“完成”和“错误”两种状态,不会返回具体的结果。Single
:其回调为onSuccess
和onError
,查询成功会在onSuccess
中返回结果,需要注意的是,如果未查询到结果,即查询结果为空,会直接走onError
回调,抛出EmptyResultSetException
异常。Maybe
:其回调为onSuccess
,onError
,onComplete
,查询成功,如果有数据,会先回调onSuccess
再回调onComplete
,如果没有数据,则会直接回调onComplete
。Flowable/Observable
:这俩相信不用多介绍了,这是返回一个可观察的对象,每当查询语句查询的部分有变化时,都会回调它的onNext
方法,直到Rx流断开。
这次学习Room&RxJava操作倒是有一波意外之喜,认识了Completable
,Single
和Maybe
三个新玩意儿。根据其特性来看,我个人更倾向于使用Single
,原因有二:
- 我目前业务中进行的CRUD操作多为一次性操作,基本不需要持续观察数据库变化。
- 不管我查询有没有结果,总得给我个响啊,像Maybe这种,结果为空就直接连
onSuccess
都不回调了,这是万万不能接受的。
当然,关于Maybe
这样的查询无结果也想要走onSuccess
回调的话,还是有补救方法的,下文会提到。
CRUD基本操作
注意:以下分类是按照SQL操作类型分类,而不是按Room的注解来分类的。
这里我们使用User
实体类来举例说明。
@Entity
data class User(
@PrimaryKey
val firstName: String,
val lastName: String,
val age: Int
)
INSERT
@Insert
fun insert(user: User): Completable
@Insert
fun insert(user: User): Single<Long>
@Insert
fun insert(user: User): Maybe<Long>
@Insert
fun insert(vararg users: User): Single<List<Long>>
@Insert
fun insert(vararg users: User): Maybe<List<Long>>
@Query("INSERT INTO user VALUES(:firstName, :lastName, :age)")
fun insert(firstName: String, lastName: String, age: Int): Single<Long>
@Query("INSERT INTO user VALUES(:firstName, :lastName, :age)")
fun insert(firstName: String, lastName: String, age: Int): Maybe<Long>
注意:
- 返回的类型是
Long
也只能是Long
,否则无法通过编译。 - 返回的
Long
值,是指的插入的行id。
UPDATE/DELETE
@Update/Delete
fun insert(user: User): Completable
@Update/Delete
fun insert(user: User): Single<Int>
@Update/Delete
fun insert(user: User): Maybe<Int>
@Update/Delete
fun insert(vararg users: User): Single<List<Int>>
@Update/Delete
fun insert(vararg users: User): Maybe<List<Int>>
@Query("UPDATE user SET firstname = :firstName WHERE age = :age")
fun updateFirstName(age: Int, firstName: String): Single<List<Int>>
@Query("UPDATE user SET firstname = :firstName WHERE age = :age")
fun updateFirstName(age: Int, firstName: String): Maybe<List<Int>>
注意:
- 返回的类型为
Integer
也只能是Integer
,否则无法通过编译。 - 返回的
Integer
值,指的是该次操作影响到的总行数,比如该次操作更新了5条,就返回5
。
QUERY
@Query("SELECT * FROM user")
fun query(): Single<List<User>>
@Query("SELECT * FROM user")
fun query(): Maybe<List<User>>
@Query("SELECT * FROM user")
fun query(): Flowable<List<User>>
@Query("SELECT * FROM user")
fun query(): Observable<List<User>>
@Query("SELECT * FROM user LIMIT 1")
fun query(): Single<User>
@Query("SELECT * FROM user LIMIT 1")
fun query(): Maybe<User>
@Query("SELECT * FROM user LIMIT 1")
fun query(): Flowable<User>
@Query("SELECT * FROM user LIMIT 1")
fun query(): Observable<User>
QUERY操作就没啥好说的了,简单明了,一看就懂。
总结
关于Maybe查询不到结果直接回调onComplete
的问题
其实是有补救方法的,你可以操作单个对象的时候假装自己在操作一个列表,比如这样:
@Insert
fun insert(vararg users: User): Maybe<List<Long>>
或者虽然操作的是单个对象,但是强行叫它返回列表的结果,像这样:
@Insert
fun insert(user: User): Maybe<List<Long>>
经过这样一番操作后,如果查询到的结果为空,还是会回调onSuccess
,会给你一个空列表[]
但是还是建议使用Single
。
关于Single的使用,可以用扩展函数来方便的进行线程切换
这里提供一个我自用的简单的切换线程的扩展函数:
/**
* 数据库一次性查询结果扩展方法,目的是处理数据库查询返回的Single结果
* 有结果调用onSuccess,空结果或查询出错一律调用onFailed
*/
fun <T> Single<T>.subscribeDbResult(onSuccess: (data: T) -> Unit, onFailed: (e: Throwable) -> Unit) {
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
onSuccess(t)
}
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
onFailed(e)
}
})
}
进行查询的时候就可以这样调用了:
userDao.getAll()
.subscribeDbResult({
onSuccess(it)
}, {
onFailed(it)
})
关于@Query
返回Single和Maybe类型的Bug
目前改版本被我试出来一个bug,那就是用@Query
的操作,在进行UPDATE
,DELETE
和INSERT
操作时,直接通过RxJava进行线程切换是没用的,像下面这样写:
@Query("UPDATE user SET firstname = :firstName WHERE age = :age")
fun updateFirstName(age: Int, firstName: String): Single<List<Int>>
userDao.updateFirstName(18, "Xu")
subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<T> {
override fun onSuccess(t: T) {
onSuccess(t)
}
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
onFailed(e)
}
})
这样会直接抛出java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
异常。
这就需要这样:
Single.just(Any())
.flatMap {
return@flatMap userDao.updateFirstName(18, "Xu")
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : SingleObserver<Int> {
override fun onSuccess(t: Int) {
}
override fun onSubscribe(d: Disposable) {
}
override fun onError(e: Throwable) {
}
})
这个bug已经被Google注意到了,预计会在下一个alpha版本,即Room 2.1.0-alpha05
中修复,详情见Defeered @Query with insert, update or delete throwing main thread exception.