【Jetpack】Room + ViewModel + LiveData 综合使用 ( 核心要点说明 | 组合方式 | 代码示例 )





一、Room + ViewModel + LiveData 框架使用核心要点




1、Room 框架优化分析


在上一篇博客 【Jetpack】使用 Room 框架访问 Android 平台 SQLite 数据库 ( 导入依赖 | 定义 Entity 实体类 | 定义 Dao 数据库访问对象接口 | 定义数据库实例类 ) 中 , 实现了 使用 Room 框架访问 Android 中的 SQLite 数据库的操作 , 每当数据库中的数据发生变化时 , 就需要开启线程 , 重新获取数据库中的数据 ;

为了优化上述问题 , 可以引入 LiveData ViewModel ,

  • ViewModel 是 视图 View数据模型 Model 之间 数据交互的 桥梁 ;
  • LiveData 是基于 ViewModel 的 , 是 对 ViewModel 数据维护的一个补充 ;

在 ViewModel 中使用了 LiveData 后 ,

先调用 LiveData#observe 函数 为 LiveData 设置 androidx.lifecycle.Observer 监听器 ,

如果 该监听器 监听到了 LiveData 数据变化 ,

直接 回调 androidx.lifecycle.Observer 监听器 的 androidx.lifecycle.Observer#onChanged 函数 ,

最终在上述回调函数中执行 查询数据库 和 更新视图 操作 ;


2、Google 官方建议的 Room + ViewModel + LiveData 架构


下图是 Google 官方 提出的 Room + ViewModel + LiveData 架构设计 建议 :

在这里插入图片描述
下面分析上述 架构图 中的 架构分层 ;


Model 数据模型层 :

  • 本地数据访问 : 使用 Room 框架 访问本地的 SQLite 数据库 ;
  • 远程数据访问 : 使用 Retrofit 框架 访问 远程数据源 ( Remote Data Source ) ;

Repository 层 : 该层负责 封装 Model 数据模型层 , 用于与 ViewModel 层进行交互 ;

ViewModel 视图模型层 : 该层 不与 Room 和 Retrofit 直接交互 , 而是与 Repository 层 进行交互 ; 在 ViewModel 层引入 LiveData 监听数据变化 , 如果数据发生变化则在 LiveData 设置的 androidx.lifecycle.Observer 监听器回调中 更新 View 视图 ;

View 视图层 : Activity / Fragment 负责视图显示的 系统组件 , 负责维护 Android 视图组件 , 显示的数据由 ViewModel 提供 ;


3、Room 与 LiveData 结合使用要点


对于 Room 框架使用来说 ,

  • Room 与 LiveData 结合使用 ,
  • Room 单独使用 ,

唯一的区别是 Room 框架中的 Dao 数据访问接口对象 中的 查询方法 , 其返回值类型改为 LiveData 类型 , LiveData 的泛型为 原来的查询方法的返回值类型 ;

Dao 查询方法的返回值由 List<Student> 变为 LiveData<List<Student>> ;


Room 框架中 , Entity 实体类 , Database 数据库实体类 , 定义方式保持不变 ,

  • Entity 实体类 使用 @Entity 注解修饰 , 并使用 @PrimaryKey 注解修饰主键 , 使用 @ColumnInfo 注解 修饰普通字段 , 使用 @Ignore 注解 修饰不需要的字段或方法 ;
  • Database 数据库实体类 使用 @Database 注解修饰该类 , 其中定义 获取 Dao 数据库访问对象的抽象方法 , 以及 将该抽象类设置成 单例类 , 在单例对象初始化时创建数据库 ;

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 数据库访问对象接口 | 定义数据库实例类 ) 博客 ;


4、Repository 层核心要点


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()
    }

5、ViewModel + Room 结合使用


根据 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)
    }

6、Activity 组件中 ViewModel 使用要点


在 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)
        }

7、Room 框架主动查询数据库数据需保留除 LiveData 返回值外的正常查询方法


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 返回值的查询方法 , 也就是如下面的代码所示 , 同时维护两组查询方法接口 ,

  • 与 LiveData 交互的接口 , 返回 LiveData<List<Student>> 类型 返回值 ;
  • 手动主动调用的查询 数据库的 方法接口 , 返回 List<Student> 类型 返回值 ;
    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun query(): LiveData<List<Student>>

    /**
     * 查询数据库表
     */
    @Query("select * from student")
    fun queryList(): List<Student>




二、完整代码示例




1、build.gradle 构建脚本


在 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'
}

2、Room 框架相关代码


Entity 实体类

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)"
    }
}

Dao 数据库访问接口对象

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 数据库实体类 使用 @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;
        }
    }
}

3、Repository 代码


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)
    }
}

4、ViewModel 代码


负责数据的维护 , 显示到 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)
    }
}

5、Activity 组件中的最终调用代码


通过调用 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)
        }
    }
}

6、执行结果


由下面的执行结果可以得出如下结论 :

  • 为 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)
            }
        })
  • 第一次 插入数据 S1 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=1, name='Tom', age=18)] ;
  • 第二次 插入数据 S2 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=1, name='Tom', age=18), Student(id=2, name='Jerry', age=16)] ;
  • 更新数据 S2 时 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=1, name='Tom', age=18), Student(id=2, name='Jack', age=60)] , id 为 2 的数据内容发生了改变 ;
  • 删除 id = 1 的数据 , 数据库数据发生改变 , 自动触发 Observer#onChanged 回调 , 此时数据库中有数据 [Student(id=2, name='Jack', age=60)] ;
  • 调用 Dao 中返回 LiveData 的接口方法查询数据库 , 返回 androidx.room.RoomTrackingLiveData@8726677 , 但其中的数据为空 ;
  • 调用 Dao 中返回 List<Student> 的接口方法查询数据库 , 返回数据为 [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)]

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/han1202012/article/details/130816155