上期我们聊到room,本期就来简单说一下room的用法。
常规room我们不聊怎么用了,跟着官方文档一步一步使用即可。
传送门
老规矩,先上效果。
代码语言:text复制 override fun testRoom() {
//常规flow监听
lifecycleScope.launchWhenResumed {
UserDB.getUserFlow("test1").collect {
Log.v("collect", "collect: $it")
}
}
//常规flow操作符监听
UserDB.getUserFlow("test1")
// .catch { }//错误捕获
.onEach {
Log.v("launchIn", "launchIn: $it")
}
// .catch { } //onEach中的错误捕获
.launchIn(lifecycleScope).start()
//转化为liveData后的监听 private val userModelObs = UserDB.getUserFlow("test1").asLiveData()
//live_data_ktx 库 将flow转为livedata private val userModelObs = UserDB.getUserFlow("test1").asLiveData()
userModelObs.observe(this) {
Log.v("livedata", "livedata: $it")
}
//修改数据
lifecycleScope.launchWhenResumed {
UserDB.updateUserAsync(UserModel("test1", "test6", 5))
}
}
//打印结果
2023-08-28 15:43:44.691 23583-23583 launchIn yz.l.fm V launchIn: UserModel(uid=test1, name=test5, gender=5)
2023-08-28 15:43:44.692 23583-23583 launchIn yz.l.fm V launchIn: UserModel(uid=test1, name=test6, gender=5)
2023-08-28 15:43:44.692 23583-23583 livedata yz.l.fm V livedata: UserModel(uid=test1, name=test5, gender=5)
2023-08-28 15:43:44.692 23583-23583 livedata yz.l.fm V livedata: UserModel(uid=test1, name=test6, gender=5)
2023-08-28 15:43:44.692 23583-23583 collect yz.l.fm V collect: UserModel(uid=test1, name=test5, gender=5)
2023-08-28 15:43:44.692 23583-23583 collect yz.l.fm V collect: UserModel(uid=test1, name=test6, gender=5)
我们看到每个结果打印了两次,其中name由5变成了6,其中5是原始值,6是最后修改数据使用的值,这里就是使用flow的好处了,修改数据库直接能够反馈到所有监听flow的地方,并且flow自带生命周期,无需担心内存泄露问题。如此处理,也能让本地数据杜绝EventBus等事件总线来回传递,造成Event灾难。
下面我们一步一步来实现这个效果。
初始化room,这里我与官方处理的方式略有差异
根据我们的模块化方案,room初始化我们放置在:features:feature_common:common_room_db模块中
代码语言:text复制@SuppressLint("StaticFieldLeak")
object RoomDB {
private lateinit var context: Context
//application初始化时调用,如果采用其他的单例方式需要每次传入context,使用比较麻烦。
fun init(context: Context) {
this.context = context.applicationContext
}
val INSTANCE: AppDataBase by lazy { Holder.holder }
private object Holder {
val holder by lazy {
Room.databaseBuilder(
context.applicationContext,
AppDataBase::class.java,
"android_room_db.db" //数据库名称
)
.allowMainThreadQueries() //允许启用同步查询,即:允许主线程可以查询数据库,这个配置要视情况使用,一般不推荐同步查询
.fallbackToDestructiveMigration()//如果数据库升级失败了,删除重新创建
.enableMultiInstanceInvalidation()//多进程查询支持
// .addMigrations(MIGRATION_1_2) //数据库版本升级,MIGRATION_1_2为要执行的表格执行sql语句,例如database.execSQL("ALTER TABLE localCacheMusic ADD COLUMN time Int NOT NULL default 0 ;")
.build()
}
}
}
@Database(
entities = [UserEntity::class],
version = 1, exportSchema = false
)
@TypeConverters(value = [LocalTypeConverter::class]) //自定义数据处理转换,这里我们将list都转为json存储
abstract class AppDataBase : RoomDatabase() {
abstract fun userDao(): UserDao
}
//本类可以根据具体业务需求自行处理,这里随便写了个demo,没有经过测试。
open class LocalTypeConverter {
@TypeConverter
fun json2StrListEntity(src: String?): List<String>? =
src.toObject()
@TypeConverter
fun strList2Json(data: List<String>?): String = gson.toJson(data ?: mutableListOf<String>())
@TypeConverter
fun date2Long(date: Date?): Long {
return date?.time ?: System.currentTimeMillis()
}
@TypeConverter
fun long2Date(time: Long): Date {
return Date(time)
}
}
接下来我们创建table,这里我们将数据库模型与实际使用的模型完全隔离开,并且使用扩展方法进行数据转换处理,避免业务模型变更影响到数据变更,到时候维护起来比较麻烦。并且难以做数据库升级。本文中所有entity结尾的类为数据库模型,model结尾的类为业务模型。
根据我们的模块化方案,其中Entity放置在:features:feature_common:common_room_db模块中,Model类及转换类放置在data_xxxx模块中,依赖关系为,data_xxxxx implementation project(":features:feature_common:common_room_db")
代码语言:text复制@Entity(primaryKeys = ["uid", "remoteName"])
data class UserEntity(
var uid: String,
var remoteName: String = "",
val name: String = "",
val gender: Int = 1
)
data class UserModel(
var uid: String = "",
var name: String = "",
var gender: Int = 1
)
//数据转换
fun UserModel.toEntity(remoteName: String) =
UserEntity(uid = this.uid, remoteName = remoteName, name = this.name, gender = gender)
fun UserEntity.toUserModel() = UserModel(uid, name, gender)
然后我们创建查询dao,room基本用法,不懂可以查看下上述的官网说明。
根据我们的模块化方案,dao存储在:features:feature_common:common_room_db模块中
代码语言:text复制//这里注意,增删改查都可以使用@Query操作符,只需要在后边写上需要操作的语句即可
//例如 @Query("DELETE FROM UserEntity") 也可以正常执行。
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveAsync(userEntity: UserEntity)
@Update
suspend fun updateAsync(userEntity: UserEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveSync(userEntity: UserEntity)
@Delete
suspend fun deleteAsync(userEntity: UserEntity)
@Query("SELECT * FROM UserEntity WHERE remoteName=:remoteName")
suspend fun getUserListAsync(remoteName: String): List<UserEntity>
@Query("SELECT * FROM UserEntity WHERE remoteName=:remoteName")
fun getUserListFlow(remoteName: String): Flow<List<UserEntity>>
@Query("SELECT * FROM UserEntity WHERE uid=:uid")
suspend fun getUserAsync(uid: String): UserEntity?
@Query("SELECT * FROM UserEntity WHERE uid=:uid")
fun getUserSync(uid: String): UserEntity?
@Query("SELECT * FROM UserEntity WHERE uid=:uid")
fun getUserFlow(uid: String): Flow<UserEntity?>
}
然后我们在data_xxxx模块中创建代理查询类,并提供将业务模型转为数据库模型&数据库模型转为业务模型的代理,方便使用。
代码如下
代码语言:text复制//这里我列举了 Async异步方式,Sync同步方式及flow方式进行数据的增删改查。
//sync方式需要创建room时调用allowMainThreadQueries(),否则会报错
//Async方式需要在协程中使用。
//flow需要协程的scope支持,尽量使用activity&fragment中的lifecycleScope来处理
object UserDB {
private val dao: UserDao by lazy {
RoomDB.INSTANCE.userDao()
}
suspend fun saveUserAsync(user: UserModel) {
dao.saveAsync(user.toEntity("test"))
}
suspend fun updateUserAsync(user: UserModel) {
dao.updateAsync(user.toEntity("test"))
}
fun saveUserSync(user: UserModel) {
dao.saveSync(user.toEntity("test"))
}
suspend fun getUserAsync(uid: String): UserModel? {
return dao.getUserAsync(uid)?.toUserModel()
}
fun getUserSync(uid: String): UserModel? {
return dao.getUserSync(uid)?.toUserModel()
}
fun getUserFlow(uid: String): Flow<UserModel?> {
return dao.getUserFlow(uid).map {
it?.toUserModel()
}
}
}
如此,我们便达到了文章开头的使用方式。
完整项目地址