Jetpack之Room的使用,结合Flow

简介: Jetpack之Room的使用,结合Flow

准备工作

Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。


依赖

如需在应用中使用Room,请将以下依赖项添加到应用的 build.gradle文件。


dependencies {
  def room_version = "2.2.5"
  implementation "androidx.room:room-runtime:$room_version"
  kapt "androidx.room:room-compiler:$room_version"
  // optional - Kotlin Extensions and Coroutines support for Room
  implementation "androidx.room:room-ktx:$room_version"
  // optional - Test helpers
  testImplementation "androidx.room:room-testing:$room_version"
}

主要组件

数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。

使用 @Database注释的类应满足以下条件:

是扩展 RoomDatabase 的抽象类。

在注释中添加与数据库关联的实体列表。

包含具有 0 个参数且返回使用@Dao注释的类的抽象方法。

在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder()获取 Database的实例。

Entity:表示数据库中的表。

DAO:包含用于访问数据库的方法。

应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。


关系如图:

image.png


ok,基本概念了解之后,看一下具体是怎么搞的。


Entity

@Entity(tableName = "t_history")
data class History(
    /**
     * @PrimaryKey主键,autoGenerate = true 自增
     * @ColumnInfo 列 ,typeAffinity 字段类型
     * @Ignore 忽略
     */
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id", typeAffinity = ColumnInfo.INTEGER)
    val id: Int? = null,
    @ColumnInfo(name = "name", typeAffinity = ColumnInfo.TEXT)
    val name: String?,
    @ColumnInfo(name = "insert_time", typeAffinity = ColumnInfo.TEXT)
    val insertTime: String?,
    @ColumnInfo(name = "type", typeAffinity = ColumnInfo.INTEGER)
    val type: Int = 1
)

Entity对象对应一张表,使用@Entity注解,并声明你的表名即可

@PrimaryKey 主键,autoGenerate = true 自增

@ColumnInfo 列,并声明列名 ,typeAffinity 字段类型

@Ignore 声明忽略的对象

很简单的一张表,主要是name和insertTime字段。


DAO

@Dao
interface HistoryDao {
    //按类型 查询所有搜索历史
    @Query("SELECT * FROM t_history WHERE type=:type")
    fun getAll(type: Int = 1): Flow<List<History>>
    @ExperimentalCoroutinesApi
    fun getAllDistinctUntilChanged() = getAll().distinctUntilChanged()
    //添加一条搜索历史
    @Insert
    fun insert(history: History)
    //删除一条搜索历史
    @Delete
    fun delete(history: History)
    //更新一条搜索历史
    @Update
    fun update(history: History)
    //根据id 删除一条搜索历史
    @Query("DELETE FROM t_history WHERE id = :id")
    fun deleteByID(id: Int)
    //删除所有搜索历史
    @Query("DELETE FROM t_history")
    fun deleteAll()
}

@Insert:增

@Delete:删

@Update:改

@Query:查

这里有一个点需要注意的,就是查询所有搜索历史返回的集合我用Flow修饰了。


只要是数据库中的任意一个数据有更新,无论是哪一行数据的更改,那就重新执行 query操作并再次派发Flow。


同样道理,如果一个不相关的数据更新时,Flow也会被派发,会收到与之前相同的数据。


这是因为 SQLite 数据库的内容更新通知功能是以表 (Table) 数据为单位,而不是以行 (Row) 数据为单位,因此只要是表中的数据有更新,它就触发内容更新通知。Room 不知道表中有更新的数据是哪一个,因此它会重新触发 DAO 中定义的 query 操作。您可以使用 Flow 的操作符,比如 distinctUntilChanged 来确保只有在当您关心的数据有更新时才会收到通知。


//按类型 查询所有搜索历史
    @Query("SELECT * FROM t_history WHERE type=:type")
    fun getAll(type: Int = 1): Flow<List<History>>
    @ExperimentalCoroutinesApi
    fun getAllDistinctUntilChanged() = getAll().distinctUntilChanged()

数据库

@Database(entities = [History::class], version = 1)
abstract class HistoryDatabase : RoomDatabase() {
    abstract fun historyDao(): HistoryDao
    companion object {
        private const val DATABASE_NAME = "history.db"
        private lateinit var mPersonDatabase: HistoryDatabase
        //注意:如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。
        //每个 RoomDatabase 实例的成本相当高,而您几乎不需要在单个进程中访问多个实例
        fun getInstance(context: Context): HistoryDatabase {
            if (!this::mPersonDatabase.isInitialized) {
                //创建的数据库的实例
                mPersonDatabase = Room.databaseBuilder(
                    context.applicationContext,
                    HistoryDatabase::class.java,
                    DATABASE_NAME
                ).build()
            }
            return mPersonDatabase
        }
    }
}

使用@Database注解声明

entities 数组,对应此数据库中的所有表

version 数据库版本号

注意:


如果您的应用在单个进程中运行,在实例化 AppDatabase 对象时应遵循单例设计模式。 每个 RoomDatabase

实例的成本相当高,而您几乎不需要在单个进程中访问多个实例。


使用

在需要的地方获取数据库


mHistoryDao = HistoryDatabase.getInstance(this).historyDao()

获取搜索历史

 

private fun getSearchHistory() {
        MainScope().launch(Dispatchers.IO) {
            mHistoryDao.getAll().collect {
                withContext(Dispatchers.Main){
                    //更新ui
                }
            }
        }
    }

collect 是Flow获取数据的方式,并不是唯一方式,可以查看文档。


为什么放在协程里面呢,因为数据库的操作是费时的,而协程可以轻松的指定线程,这样不阻塞UI线程。


查看Flow源码也发现,Flow是协程包下的


package kotlinx.coroutines.flow

以collect为例,也是被suspend 修饰的,既然支持挂起,那配合协程岂不美哉。


 

@InternalCoroutinesApi
    public suspend fun collect(collector: FlowCollector<T>)

保存搜索记录

 

private fun saveSearchHistory(text: String) {
        MainScope().launch(Dispatchers.IO) {
            mHistoryDao.insert(History(null, text, DateUtils.longToString(System.currentTimeMillis())))
        }
    }
清空本地历史
    private fun cleanHistory() {
        MainScope().launch(Dispatchers.IO) {
            mHistoryDao.deleteAll()
        }
    }

作者:https://blog.csdn.net/yechaoa


数据库升级

数据库升级是一个重要的操作,毕竟可能会造成数据丢失,也是很严重的问题。


Room通过Migration类来执行升级的操作,我们只要告诉Migration类改了什么就行,比如新增字段或表。


定义Migration类

 

/**
     * 数据库版本 1->2 t_history表格新增了updateTime列
     */
    private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("ALTER TABLE t_history ADD COLUMN updateTime String")
        }
    }
    /**
     * 数据库版本 2->3 新增label表
     */
    private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
        override fun migrate(database: SupportSQLiteDatabase) {
            database.execSQL("CREATE TABLE IF NOT EXISTS `t_label` (`id` INTEGER PRIMARY KEY autoincrement, `name` TEXT)")
        }
    }

Migration接收两个参数:


startVersion 旧版本

endVersion 新版本

通知数据库更新

mPersonDatabase = Room.databaseBuilder(
        context.applicationContext,
        HistoryDatabase::class.java,
        DATABASE_NAME
    ).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
        .build()

完整代码

@Database(entities = [History::class, Label::class], version = 3)
abstract class HistoryDatabase : RoomDatabase() {
    abstract fun historyDao(): HistoryDao
    companion object {
        private const val DATABASE_NAME = "history.db"
        private lateinit var mPersonDatabase: HistoryDatabase
        fun getInstance(context: Context): HistoryDatabase {
            if (!this::mPersonDatabase.isInitialized) {
                //创建的数据库的实例
                mPersonDatabase = Room.databaseBuilder(
                    context.applicationContext,
                    HistoryDatabase::class.java,
                    DATABASE_NAME
                ).addMigrations(MIGRATION_1_2, MIGRATION_2_3)
                    .build()
            }
            return mPersonDatabase
        }
        /**
         * 数据库版本 1->2 t_history表格新增了updateTime列
         */
        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("ALTER TABLE t_history ADD COLUMN updateTime String")
            }
        }
        /**
         * 数据库版本 2->3 新增label表
         */
        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("CREATE TABLE IF NOT EXISTS `t_label` (`id` INTEGER PRIMARY KEY autoincrement, `name` TEXT)")
            }
        }
    }
}

注意:

@Database注解中版本号的更改,如果是新增表的话,entities参数里也要添加上。


建议升级操作顺序

修改版本号 -> 添加Migration -> 添加给databaseBuilder


配置编译器选项

Room 具有以下注解处理器选项:


room.schemaLocation:配置并启用将数据库架构导出到给定目录中的 JSON 文件的功能。如需了解详情,请参阅 Room 迁移。

room.incremental:启用 Gradle 增量注释处理器。

room.expandProjection:配置 Room 以重写查询,使其顶部星形投影在展开后仅包含 DAO 方法返回类型中定义的列。

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments += [
                    "room.schemaLocation":"$projectDir/schemas".toString(),
                    "room.incremental":"true",
                    "room.expandProjection":"true"]
            }
        }
    }
}

配置好之后,编译运行,module文件夹下会生成一个schemas文件夹,其下有一个json文件,里面包含数据库的基本信息。


{
  "formatVersion": 1,
  "database": {
    "version": 1,
    "identityHash": "xxx",
    "entities": [
      {
        "tableName": "t_history",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `insert_time` TEXT, `type` INTEGER NOT NULL)",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": false
          },
          {
            "fieldPath": "name",
            "columnName": "name",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "insertTime",
            "columnName": "insert_time",
            "affinity": "TEXT",
            "notNull": false
          },
          {
            "fieldPath": "type",
            "columnName": "type",
            "affinity": "INTEGER",
            "notNull": true
          }
        ],
        "primaryKey": {
          "columnNames": [
            "id"
          ],
          "autoGenerate": true
        },
        "indices": [],
        "foreignKeys": []
      }
    ],
    "views": [],
    "setupQueries": [
      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'xxx')"
    ]
  }
}
目录
相关文章
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
1189 0
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
|
SQL Java 数据库
安卓Jetpack组件Room数据库框架的使用(附Demo)
安卓Jetpack组件Room数据库框架的使用(附Demo)
335 0
安卓Jetpack组件Room数据库框架的使用(附Demo)
|
设计模式 缓存 自然语言处理
Jetpack 系列(4)—— 有小伙伴说看不懂 LiveData、Flow、Channel,跟我走
Jetpack 系列(4)—— 有小伙伴说看不懂 LiveData、Flow、Channel,跟我走
839 0
Jetpack 系列(4)—— 有小伙伴说看不懂 LiveData、Flow、Channel,跟我走
|
SQL 存储 XML
Jetpack Room — 给你一种新的数据库操作体验!
🏀在我们日常开发中,经常要和数据打交道,所以存储数据是很重要的事。如果你在处理数据存储这一块时想要有更好的体验,那你不妨来看看Room吧~
|
SQL 关系型数据库 API
【Android Jetpack】Room数据库的使用及原理详解
Android Jetpack的出现统一了Android开发生态,各种三方库逐渐被官方组件所取代。Room也同样如此,逐渐取代竞品成为最主流的数据库ORM框架。这当然不仅仅因为其官方身份,更是因为其良
1558 0
|
数据库
Jetpack Room使用
Jetpack Room使用
93 0
|
4月前
|
存储 安全 Android开发
构建高效的Android应用:Kotlin与Jetpack的结合
【5月更文挑战第31天】 在移动开发的世界中,Android 平台因其开放性和广泛的用户基础而备受开发者青睐。随着技术的进步和用户需求的不断升级,开发一个高效、流畅且易于维护的 Android 应用变得愈发重要。本文将探讨如何通过结合现代编程语言 Kotlin 和 Android Jetpack 组件来提升 Android 应用的性能和可维护性。我们将深入分析 Kotlin 语言的优势,探索 Jetpack 组件的核心功能,并通过实例演示如何在实际项目中应用这些技术。
|
3月前
|
数据管理 API 数据库
探索Android Jetpack:现代安卓开发的利器
Android Jetpack是谷歌为简化和优化安卓应用开发而推出的一套高级组件库。本文深入探讨了Jetpack的主要构成及其在应用开发中的实际运用,展示了如何通过使用这些工具来提升开发效率和应用性能。
|
2月前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
51 4
|
3月前
|
Android开发
Jetpack Compose: Hello Android
Jetpack Compose: Hello Android