赞
踩
在上一篇博客 【Jetpack】使用 Room 框架访问 Android 平台 SQLite 数据库 ( 导入依赖 | 定义 Entity 实体类 | 定义 Dao 数据库访问对象接口 | 定义数据库实例类 ) 中 , 实现了 使用 Room 框架访问 Android 中的 SQLite 数据库的操作 , 每当数据库中的数据发生变化时 , 就需要开启线程 , 重新获取数据库中的数据 ;
为了优化上述问题 , 可以引入 LiveData 和 ViewModel ,
在 ViewModel 中使用了 LiveData 后 ,
先调用 LiveData#observe 函数 为 LiveData 设置 androidx.lifecycle.Observer 监听器 ,
如果 该监听器 监听到了 LiveData 数据变化 ,
直接 回调 androidx.lifecycle.Observer 监听器 的 androidx.lifecycle.Observer#onChanged 函数 ,
最终在上述回调函数中执行 查询数据库 和 更新视图 操作 ;
下图是 Google 官方 提出的 Room + ViewModel + LiveData 架构设计 建议 :
下面分析上述 架构图 中的 架构分层 ;
Model 数据模型层 :
Repository 层 : 该层负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;
ViewModel 视图模型层 : 该层 不与 Room 和 Retrofit 直接交互 , 而是与 Repository 层 进行交互 ; 在 ViewModel 层引入 LiveData 监听数据变化 , 如果数据发生变化则在 LiveData 设置的 androidx.lifecycle.Observer 监听器回调中 更新 View 视图 ;
View 视图层 : Activity / Fragment 负责视图显示的 系统组件 , 负责维护 Android 视图组件 , 显示的数据由 ViewModel 提供 ;
对于 Room 框架使用来说 ,
唯一的区别是 Room 框架中的 Dao 数据访问接口对象 中的 查询方法 , 其返回值类型改为 LiveData 类型 , LiveData 的泛型为 原来的查询方法的返回值类型 ;
Dao 查询方法的返回值由 List<Student> 变为 LiveData<List<Student>> ;
Room 框架中 , Entity 实体类 , Database 数据库实体类 , 定义方式保持不变 ,
在 Room 框架中的 Dao 数据库访问对象接口 的定义方式需要作出改变 , 涉及到数据库查询的 接口方法时 , 其返回值需要 返回 LiveData 类型 , 泛型设置为 List<Student> 类型 ;
/**
* 查询数据库表
*/
@Query("select * from student")
fun query(): LiveData<List<Student>>
/**
* 根据传入的 id 查询数据库表
* 在注解中使用 :id 调用参数中的 id: Int
*/
@Query("select * from student where id = :id")
fun query(id: Int): LiveData<List<Student>>
原来的方法如下 , 其查询接口的返回值是 List<Student> ;
/**
* 查询数据库表
*/
@Query("select * from student")
fun query(): List<Student>
/**
* 根据传入的 id 查询数据库表
* 在注解中使用 :id 调用参数中的 id: Int
*/
@Query("select * from student where id = :id")
fun query(id: Int): List<Student>
Room 框架的用法 , 参考 【Jetpack】使用 Room 框架访问 Android 平台 SQLite 数据库 ( 导入依赖 | 定义 Entity 实体类 | 定义 Dao 数据库访问对象接口 | 定义数据库实例类 ) 博客 ;
Repository 层负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;
因此在 Repository 中 , 需要 持有 Dao 数据访问接口对象 ;
lateinit var dao: StudentDao
而 Dao 又是通过 Database 得到的 ,
因此在 该 Repository 中需要先获取 Database 数据库实例类对象 , 然后通过 Database 获取 Dao 数据访问接口 ;
constructor(context: Context) {
var database = StudentDatabase.inst(context)
this.dao = database.studentDao()
}
此外 , 还需要 在 Repository 层中 , 维护数据库的 增删改查 方法 , 该操作直接调用 Dao 数据库访问接口对象完成 ,
fun insert(student: Student) { this.dao.insert(student) } fun query(): LiveData<List<Student>> { return this.dao.query() } fun update(student: Student) { this.dao.update(student) } fun delete(id: Int) { var student = Student(id) this.dao.delete(student) }
特别注意 , 为了 将 Room 与 LiveData 结合 , Dao 查询方法的返回值是 LiveData 类型 ;
fun query(): LiveData<List<Student>> {
return this.dao.query()
}
根据 Google 官方的架构建议 , ViewModel 不与 Room 直接交互 , 而是由 Repository 将 Room 封装起来 , 由 ViewModel 与 Repository 进行交互 ;
ViewModel 与 Room 结合使用 , 实际上与 Repository 进行交互 ;
ViewModel 需要继承 AndroidViewModel , 并且需要在类中维护 Repository 成员变量 ,
class ViewModel: AndroidViewModel {
lateinit var repository: Repository
constructor(application: Application) : super(application) {
this.repository = Repository(application)
}
同时 , 需要 在 ViewModel 中维护 数据库 的 增删改查 的对应函数 , 通过调用 Repository 成员边来那个实现对数据库的操作 , 查询函数 的返回值是 LiveData 类型的 ;
fun insert(student: Student) { this.dao.insert(student) } fun query(): LiveData<List<Student>> { return this.dao.query() } fun update(student: Student) { this.dao.update(student) } fun delete(id: Int) { var student = Student(id) this.dao.delete(student) }
在 Activity 组件中 , 通过调用 ViewModel 视图模型获取 数据库中的数据 , ViewModel 调用 Repository 层的增删改查方法 , Repository 调用 Room 框架的相关方法操作 SQLite 数据库 ;
首先 , 获取 ViewModel 视图模型 ;
// 获取 ViewModel 视图模型对象
var viewModel: ViewModel = ViewModelProvider(
this,
AndroidViewModelFactory(application)).get(ViewModel::class.java)
然后 , 为 ViewModel 视图模型中获取的 LiveData 数据设置 Observer 监听 ;
// 为 ViewModel 中获取的 LiveData 数据设置 Observer 监听
viewModel.query().observe(this, object: Observer<List<Student>> {
override fun onChanged(t: List<Student>?) {
Log.i("MainActivity", "Observer#onChanged 回调, List<Student>: " + t)
}
})
最后 , 通过调用 ViewModel 中定义的 数据库操作 方法 , 修改数据库中的数据 , 如果数据库中的数据发生了改变 , 就会自动回调 Observer#onChanged 方法 ;
thread(start = true) { Thread.sleep(500) // 插入数据 var s1 = Student("Tom", 18) var s2 = Student("Jerry", 16) viewModel.insert(s1) Log.i("MainActivity", "插入数据 S1 : " + s1) Thread.sleep(500) viewModel.insert(s2) Log.i("MainActivity", "插入数据 S2 : " + s2) Thread.sleep(500) s2 = Student(2, "Jack", 60) viewModel.update(s2) Log.i("MainActivity", "更新数据 S2 : " + s2) Thread.sleep(500) // 删除数据 viewModel.delete(1) Log.i("MainActivity", "删除数据 id = 1") Thread.sleep(500) var students = viewModel.repository.dao.query() Log.i("MainActivity", "主动查询 : LiveData : " + students + " , 实际数据 : " + students?.value) var students2 = viewModel.repository.dao.queryList() Log.i("MainActivity", "主动查询2 : " + students2) }
Room 框架 与 LiveData 结合使用之后 , 在 Room 框架中的 Dao 数据库访问接口中 定义了 LiveData 返回值类型的查询方法 ;
/**
* 查询数据库表
*/
@Query("select * from student")
fun query(): LiveData<List<Student>>
/**
* 根据传入的 id 查询数据库表
* 在注解中使用 :id 调用参数中的 id: Int
*/
@Query("select * from student where id = :id")
fun query(id: Int): LiveData<List<Student>>
上述定义的 fun query(): LiveData<List<Student>> 查询方法 , 只能在数据库数据发生改变被动回调时才能查询出数据 , 如果主动调用该方法查询数据库 , 会返回一个空数据的 LiveData ;
如果想要手动主动查询数据库 , 需要保留非 LiveData 返回值的查询方法 , 也就是如下面的代码所示 , 同时维护两组查询方法接口 ,
/**
* 查询数据库表
*/
@Query("select * from student")
fun query(): LiveData<List<Student>>
/**
* 查询数据库表
*/
@Query("select * from student")
fun queryList(): List<Student>
在 build.gradle 构建脚本 中 , 需要配置 Kotlin 插件 和 Kotlin 注解插件 ;
plugins {
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
导入 Room 依赖库 , 注意这是 Kotlin 版本需要导入的依赖库 , 如果是 Java 版本 , 需要导入另外的注解处理器 ;
// 导入 Room 依赖库
implementation 'androidx.room:room-runtime:2.2.5'
// 导入注解处理器 ( Kotlin )
kapt 'androidx.room:room-compiler:2.2.5'
完整代码 :
plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-kapt' } android { namespace 'kim.hsl.rvl' compileSdk 32 defaultConfig { applicationId "kim.hsl.rvl" minSdk 21 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } viewBinding { // 启用 ViewBinding enabled = true } } dependencies { implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' // 导入 Room 依赖库 implementation 'androidx.room:room-runtime:2.2.5' // 导入注解处理器 ( Kotlin ) kapt 'androidx.room:room-compiler:2.2.5' // 导入注解处理器 ( Java ) //annotationProcessor 'androidx.room:room-compiler:2.2.5' }
Entity 实体类 使用 @Entity 注解修饰 , 并使用 @PrimaryKey 注解修饰主键 , 使用 @ColumnInfo 注解 修饰普通字段 , 使用 @Ignore 注解 修饰不需要的字段或方法 ;
完整代码 :
package kim.hsl.rvl import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore import androidx.room.PrimaryKey /** * 定义数据库表 Entity 实体 / 同时定义数据库表 和 对鹰的实体类 * 设置该数据类对应数据库中的一张数据表, 表名为 student * 该数据库表中的数据对应一个 Student 类实例对象 */ @Entity(tableName = "student") class Student { /** * @PrimaryKey 设置主键 autoGenerate 为自增 * @ColumnInfo name 设置列名称 / typeAffinity 设置列类型 */ @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER) var id: Int = 0 /** * 姓名字段 * 数据库表中的列名为 name * 数据库表中的类型为 TEXT 文本类型 */ @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT) lateinit var name: String /** * 年龄字段 * 数据库表中的列名为 age * 数据库表中的类型为 INTEGER 文本类型 */ @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER) var age: Int = 0 /** * 有些属性用于做业务逻辑 * 不需要插入到数据库中 * 使用 @Ignore 注解修饰该属性字段 */ @Ignore lateinit var studentInfo: String /** * 默认的构造方法给 Room 框架使用 */ constructor(id: Int, name: String, age: Int) { this.id = id this.name = name this.age = age } /** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */ @Ignore constructor(name: String, age: Int) { this.name = name this.age = age } /** * 使用 @Ignore 标签标注后 * Room 就不会使用该构造方法了 * 这个构造方法是给开发者使用的 */ @Ignore constructor(id: Int) { this.id = id } override fun toString(): String { return "Student(id=$id, name='$name', age=$age)" } }
在 Room 框架中的 Dao 数据库访问对象接口 的定义方式需要作出改变 , 涉及到数据库查询的 接口方法时 , 其返回值需要 返回 LiveData 类型 , 泛型设置为 List<Student> 类型 ;
完整代码 :
package kim.hsl.rvl import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Update /** * 数据库访问对象接口 / 使用 @Dao 注解修饰 * 提供数据库的增删改查方法 */ @Dao interface StudentDao { /** * 向数据库表中插入元素 */ @Insert fun insert(student: Student) /** * 从数据库表中删除元素 */ @Delete fun delete(student: Student) /** * 修改数据库表元素 */ @Update fun update(student: Student) /** * 查询数据库表 */ @Query("select * from student") fun query(): LiveData<List<Student>> /** * 根据传入的 id 查询数据库表 * 在注解中使用 :id 调用参数中的 id: Int */ @Query("select * from student where id = :id") fun query(id: Int): LiveData<List<Student>> /** * 查询数据库表 */ @Query("select * from student") fun queryList(): List<Student> /** * 根据传入的 id 查询数据库表 * 在注解中使用 :id 调用参数中的 id: Int */ @Query("select * from student where id = :id") fun queryList(id: Int): List<Student> }
Database 数据库实体类 使用 @Database 注解修饰该类 , 其中定义 获取 Dao 数据库访问对象的抽象方法 , 以及 将该抽象类设置成 单例类 , 在单例对象初始化时创建数据库 ;
完整代码 :
package kim.hsl.rvl import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase @Database(entities = [Student::class], version = 1, exportSchema = false) abstract class StudentDatabase: RoomDatabase() { /** * 获取 数据库访问 对象 * 这是必须要实现的函数 */ abstract fun studentDao(): StudentDao companion object { lateinit var instance: StudentDatabase fun inst(context: Context): StudentDatabase { if (!::instance.isInitialized) { synchronized(StudentDatabase::class) { // 创建数据库 instance = Room.databaseBuilder( context.applicationContext, StudentDatabase::class.java, "student_database.db") .allowMainThreadQueries() // Room 原则上不允许在主线程操作数据库 // 如果要在主线程操作数据库需要调用该函数 .build() } } return instance; } } }
Repository 代码 负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;
package kim.hsl.rvl import android.content.Context import androidx.lifecycle.LiveData class Repository { lateinit var dao: StudentDao constructor(context: Context) { var database = StudentDatabase.inst(context) this.dao = database.studentDao() } fun insert(student: Student) { this.dao.insert(student) } fun query(): LiveData<List<Student>> { return this.dao.query() } fun update(student: Student) { this.dao.update(student) } fun delete(id: Int) { var student = Student(id) this.dao.delete(student) } }
负责数据的维护 , 显示到 View 组件中 ;
完整代码 :
package kim.hsl.rvl import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData class ViewModel: AndroidViewModel { lateinit var repository: Repository constructor(application: Application) : super(application) { this.repository = Repository(application) } fun insert(student: Student) { this.repository.insert(student) } fun query(): LiveData<List<Student>> { return this.repository.query() } fun update(student: Student) { this.repository.update(student) } fun delete(id: Int) { this.repository.delete(id) } }
通过调用 ViewModel 视图模型 , 访问 Room 数据库框架 , 对数据进行增删改查 , 并通过 LiveData 监听数据库中的数据 ,
如果数据库中的数据发生改变 , 自动回调 LiveData 的 Observer 监听器中的 onChanged 回调方法 ;
完整代码 :
package kim.hsl.rvl import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory import kim.hsl.rvl.databinding.ActivityMainBinding import kotlin.concurrent.thread class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(getLayoutInflater()) setContentView(binding.root) // 获取 ViewModel 视图模型对象 var viewModel: ViewModel = ViewModelProvider( this, AndroidViewModelFactory(application)).get(ViewModel::class.java) // 为 ViewModel 中获取的 LiveData 数据设置 Observer 监听 viewModel.query().observe(this, object: Observer<List<Student>> { override fun onChanged(t: List<Student>?) { Log.i("MainActivity", "Observer#onChanged 回调, List<Student>: " + t) } }) thread(start = true) { Thread.sleep(500) // 插入数据 var s1 = Student("Tom", 18) var s2 = Student("Jerry", 16) viewModel.insert(s1) Log.i("MainActivity", "插入数据 S1 : " + s1) Thread.sleep(500) viewModel.insert(s2) Log.i("MainActivity", "插入数据 S2 : " + s2) Thread.sleep(500) s2 = Student(2, "Jack", 60) viewModel.update(s2) Log.i("MainActivity", "更新数据 S2 : " + s2) Thread.sleep(500) // 删除数据 viewModel.delete(1) Log.i("MainActivity", "删除数据 id = 1") Thread.sleep(500) var students = viewModel.repository.dao.query() Log.i("MainActivity", "主动查询 : LiveData : " + students + " , 实际数据 : " + students?.value) var students2 = viewModel.repository.dao.queryList() Log.i("MainActivity", "主动查询2 : " + students2) } } }
由下面的执行结果可以得出如下结论 :
[]
打印结果 ; // 为 ViewModel 中获取的 LiveData 数据设置 Observer 监听
viewModel.query().observe(this, object: Observer<List<Student>> {
override fun onChanged(t: List<Student>?) {
Log.i("MainActivity", "Observer#onChanged 回调, List<Student>: " + t)
}
})
[Student(id=1, name='Tom', age=18)]
;[Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)]
;[Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)]
, id 为 2 的数据内容发生了改变 ;[Student(id=2, name='Jack', age=60)]
;androidx.room.RoomTrackingLiveData@8726677
, 但其中的数据为空 ;[Student(id=2, name='Jack', age=60)]
;执行结果 :
2023-05-24 16:49:49.225 I/MainActivity: Observer#onChanged 回调, List<Student>: []
2023-05-24 16:49:49.475 I/MainActivity: 插入数据 S1 : Student(id=0, name='Tom', age=18)
2023-05-24 16:49:49.481 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18)]
2023-05-24 16:49:49.979 I/MainActivity: 插入数据 S2 : Student(id=0, name='Jerry', age=16)
2023-05-24 16:49:49.981 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)]
2023-05-24 16:49:50.482 I/MainActivity: 更新数据 S2 : Student(id=2, name='Jack', age=60)
2023-05-24 16:49:50.484 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)]
2023-05-24 16:49:50.996 I/MainActivity: 删除数据 id = 1
2023-05-24 16:49:51.009 I/MainActivity: Observer#onChanged 回调, List<Student>: [Student(id=2, name='Jack', age=60)]
2023-05-24 16:49:51.497 I/MainActivity: 主动查询 : LiveData : androidx.room.RoomTrackingLiveData@8726677 , 实际数据 : null
2023-05-24 16:49:51.500 I/MainActivity: 主动查询2 : [Student(id=2, name='Jack', age=60)]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。