【安卓】Jetpack之Room的使用,结合Flow

Jetpack之Room的使用,结合Flow

李先森发布于 今天 08:12

准备工作

依赖

如需在应用中使用 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 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。

关系如图: 【安卓】Jetpack之Room的使用,结合Flow

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 声明忽略的对象

很简单的一张表,主要是nameinsertTime字段。

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 也会被派发,会收到与之前相同的数据。

    //按类型 查询所有搜索历史

@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 数据库版本号

注意:

使用

在需要的地方获取数据库

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

复制代码

获取搜索历史

    private fun getSearchHistory() {

MainScope().launch(Dispatchers.IO) {

mHistoryDao.getAll().collect {

withContext(Dispatchers.Main){

//更新ui

}

}

}

}

复制代码

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

为什么放在协程里面呢,因为数据库的操作是费时的,而协程可以轻松的指定线程,这样不阻塞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()

}

}

复制代码

数据库升级

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

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

]

}

}

复制代码

ok,基本使用讲解完了,如果对你有用,点个赞呗 ^ _ ^

程序员android移动端开发jetpack

阅读 7发布于 今天 08:12

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

avatar

李先森

0 声望

0 粉丝

0 条评论

得票时间

avatar

李先森

0 声望

0 粉丝

宣传栏

准备工作

依赖

如需在应用中使用 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 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。 最后,应用使用实体来获取和设置与数据库中的表列相对应的值。

关系如图: 【安卓】Jetpack之Room的使用,结合Flow

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 声明忽略的对象

很简单的一张表,主要是nameinsertTime字段。

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 也会被派发,会收到与之前相同的数据。

    //按类型 查询所有搜索历史

@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 数据库版本号

注意:

使用

在需要的地方获取数据库

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

复制代码

获取搜索历史

    private fun getSearchHistory() {

MainScope().launch(Dispatchers.IO) {

mHistoryDao.getAll().collect {

withContext(Dispatchers.Main){

//更新ui

}

}

}

}

复制代码

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

为什么放在协程里面呢,因为数据库的操作是费时的,而协程可以轻松的指定线程,这样不阻塞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()

}

}

复制代码

数据库升级

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

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

]

}

}

复制代码

ok,基本使用讲解完了,如果对你有用,点个赞呗 ^ _ ^

以上是 【安卓】Jetpack之Room的使用,结合Flow 的全部内容, 来源链接: utcz.com/a/107905.html

回到顶部