快速了解Android Room使用细则进阶
作者:AlbertZein
1、前言
上一篇5分钟带你了解Android Room数据好起来了,有人催更,遂决定再写一篇Room的使用,这篇我们着重讲讲注解。如果写的不好,或者有错误之处,恳请在评论、私信、邮箱指出,万分感谢🙏
2、@ForeignKey和@PrimaryKey
考验你数据库知识的时候来了!因为你会频繁看到@PrimaryKey
所以先讲它
@ForeignKey
注解用于定义外键关系,它指定了一个实体类中的一个字段是另一个实体类的主键。这种关系被称为“外键关系”,并且可以用于在多个表之间建立关联。
例如,如果有两个实体类 User
和 Address
,并且想要将它们关联起来,则可以使用 @ForeignKey
注解来指定 Address
中的 user_id
字段是 User
的主键:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String ) @Entity(tableName = "addresses", foreignKeys = [ ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["user_id"], onDelete = ForeignKey.CASCADE) ]) data class Address( @PrimaryKey val id: Int, val street: String, val city: String, val state: String, val zip: String, @ColumnInfo(name = "user_id") val userId: Int )
在这个例子中,我们使用 @ForeignKey
注解将 Address
中的 user_id
字段指定为 User
的主键。这将创建一个外键关系,确保在插入或更新 Address
表中的数据时,user_id
字段的值必须是 User
表中存在的主键值之一。
@PrimaryKey
注解用于指定实体类中的一个字段是主键。主键是用于唯一标识每个实体类对象的字段。在 Room 中,每个实体类必须至少有一个字段被指定为主键。
例如,如果有一个实体类 User
,并且想要将 id
字段指定为主键,则可以使用 @PrimaryKey
注解:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String )
在这个例子中,我们使用 @PrimaryKey
注解将 id
字段指定为 User
实体类的主键。这将确保在插入或更新 User
表中的数据时,每个 id
字段的值都是唯一的。
3、@TypeConverters
在使用Room时,你可能会遇到需要在Entity类中使用非基本类型的情况,例如Date、Calendar、List等类型。在这种情况下,你可以使用TypeConverters将这些类型转换为Room可以存储的类型。在Room中,可以使用@TypeConverter注解来定义一个类型转换器,例如:
class Converters { @TypeConverter fun fromDate(date: Date): Long { return date.time } @TypeConverter fun toDate(timestamp: Long): Date { return Date(timestamp) } @TypeConverter fun fromList(list: List<String>?): String? { return list?.joinToString(",") } @TypeConverter fun toList(string: String?): List<String>? { return string?.split(",") } } @Entity(tableName = "user") @TypeConverters(Converters::class) data class User( @PrimaryKey val id: Int, val name: String, val birthday: Date @TypeConverters(HobbiesConverter::class) val hobbies: List<String> ) @Database(entities = [User::class], version = 1) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { // ... } @Dao @TypeConverters(Converters::class) interface UserDao { @Query("SELECT * FROM user") fun getAll(): List<User> }
示例代码在非常多的地方使用了@TypeConverters
,不同的位置造成的影响也是不同的,实际上可以应用到以下四个地方:
- 实体类:在
@Entity
注解中使用,可以在处理该实体类时使用它们。 - DAO 接口:在 DAO 接口中使用,可以在执行该 DAO 中的查询时使用它们。
- 数据库类:在
RoomDatabase
类中使用, 可以在整个数据库中使用它们。 - 实体类中的属性:在实体类中的属性,可以在处理该属性时使用指定的类型转换器
4、@Relation
@Relation
用于在实体类之间建立关系。它可以用于定义两个或更多实体之间的关系,这些实体可以在数据库中分别存储在不同的表中。
@Relation
注解应该与 @Query
注解一起使用,以便 Room 可以在查询结果中返回相关实体之间的关系。@Relation
注解的一个常见用例是定义父子关系,其中一个实体包含对另一个实体的引用。
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, val email: String ) @Entity(tableName = "books") data class Book( @PrimaryKey val id: Int, val title: String, val author: String, val userId: Int ) data class UserWithBooks( @Embedded val user: User, @Relation( parentColumn = "id", entityColumn = "userId" ) val books: List<Book> )
在这个示例中,我们有两个实体类 User
和 Book
,它们之间有一个父子关系,其中一个用户可以拥有多本书。然后,我们定义了一个 UserWithBooks
数据类,它包含一个嵌套的 User
实体和一个 @Relation
注解,用于指定如何检索与该用户关联的所有书籍。@Relation
注解包括 parentColumn
和 entityColumn
参数,分别指定父实体的主键列和子实体的外键列。
当我们使用 @Relation
注解时,我们需要在查询中使用 SELECT
语句,以便 Room 可以检索相关的实体。例如,在 Dao
接口中,我们可以使用以下查询:
//Transaction下一点就会说 @Transaction @Query("SELECT * FROM users WHERE id = :id") fun getUserWithBooks(id: Int): UserWithBooks
此外,我们使用 SELECT *
语句来检索所有用户属性和相关的书籍列表,因为 UserWithBooks
数据类包含一个嵌套的 User
实体和一个 List<Book>
列表。
5、@Transaction
第4点说到@Relation
时使用到了@Transaction
。在这个查询中,我们使用 @Transaction
注解来确保整个查询作为一个事务执行,以便 Room 可以在单个操作中检索 User
实体和与之相关的所有 Book
实体。
@Transaction
用于将一组数据库操作包装在一个事务中。它可以确保在执行数据库操作时保持数据库的一致性,并在必要时回滚事务以确保数据的完整性。
在 Room 中,单个数据库操作(例如插入、更新或删除)是自动运行在事务中的。但是,当需要执行多个数据库操作时,可能需要手动创建一个事务来确保这些操作原子性地执行。如果需要执行多个数据库操作,请始终考虑使用 @Transaction
注解。这可以避免数据不一致和其他与数据库操作相关的问题。
6、@Embedded
上一篇,有同志说@Embedded很好用,的确如此哈
@Embedded
用于指定一个实体类中的一个或多个字段应该作为其所属的另一个实体的嵌入式对象。这使得 Room 可以将多个相关实体的数据组合成一个单独的对象,从而简化了数据库操作。
当在一个实体类中使用 @Embedded
注解时,可以指定该实体类中的一个或多个字段应该嵌入到另一个实体类中。例如,假设有一个 Address
实体类和一个 User
实体类,其中 User
实体类包含一个 Address
对象。可以使用 @Embedded
注解将 Address
对象嵌入到 User
实体类中:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, @Embedded val address: Address ) data class Address( val street: String, val city: String, val state: String, val zip: String )
在这个例子中,User
实体类包含一个 Address
对象,它使用 @Embedded
注解指定了该对象应该嵌入到 User
实体类中。在查询数据库时,Room 将自动组合 User
实体类和 Address
实体类中的字段,以便可以轻松地访问和操作它们。
还可以使用 prefix
参数来指定 Room 应该在组合两个实体类中的字段时使用的前缀。例如:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, @Embedded(prefix = "home_") val homeAddress: Address, @Embedded(prefix = "work_") val workAddress: Address )
在这个例子中,User
实体类包含两个 Address
对象,一个是 homeAddress
,另一个是 workAddress
。我们使用 @Embedded(prefix = "home_")
和 @Embedded(prefix = "work_")
注解为每个地址对象指定了不同的前缀。这使得 Room 可以区分两个地址对象中的相同字段,并将它们组合成一个单独的对象。
当然你也可以这么干
@Entity(tableName data class User( @PrimaryKey val id: Int, val name: String, @Embedded @ColumnInfo(name = "home_address") val homeAddress: Address )
7、@ColumnInfo
可以看到,我们刚刚用到了@ColumnInfo
这个注解,用于自定义实体类中的列名、默认值和其他属性。当需要将一个实体类映射到数据库表时,可以使用 @ColumnInfo
注解来指定实体类中的每个字段在数据库表中的名称和其他属性。
(1)指定实体类中的字段名称
@ColumnInfo
注解最常用的用途是指定实体类中的字段名称。例如:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, @ColumnInfo(name = "full_name") val name: String, val age: Int )
在这个例子中,我们使用 @ColumnInfo(name = "full_name")
将 name
字段的名称指定为 full_name
。这意味着在数据库表中,这个字段将被命名为 full_name
,而不是 name
。
(2)指定实体类中的字段默认值
@ColumnInfo
注解还可以用于指定实体类中的字段默认值。例如:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, @ColumnInfo(name = "full_name") val name: String, @ColumnInfo(name = "is_active") val isActive: Boolean = true )
在这个例子中,我们使用 @ColumnInfo(name = "is_active")
将 isActive
字段的名称指定为 is_active
,并将其默认值设置为 true
。这意味着在数据库表中,这个字段将被命名为 is_active
,并且默认值将为 true
。
(3)指定实体类中的字段约束
@ColumnInfo
注解还可以用于指定实体类中的字段约束。例如:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, @ColumnInfo(name = "full_name") val name: String, @ColumnInfo(name = "is_active") val isActive: Boolean = true, @ColumnInfo(name = "created_at", defaultValue = "CURRENT_TIMESTAMP") val createdAt: String )
在这个例子中,我们使用 @ColumnInfo(name = "created_at", defaultValue = "CURRENT_TIMESTAMP")
将 createdAt
字段的名称指定为 created_at
,并将其默认值设置为 CURRENT_TIMESTAMP
。这意味着在数据库表中,这个字段将被命名为 created_at
,并且默认值将为 CURRENT_TIMESTAMP
。
8、@Ignore
很直观的注解哈。指定实体类中应该忽略的字段。当需要在实体类中添加一个字段,但不想将其映射到数据库表中时,可以使用 @Ignore
注解来指定该字段应该被忽略。
忽略一个实体类中的字段
@Ignore
注解最常用的用法是忽略一个实体类中的字段,从而防止该字段被映射到数据库表中。例如:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, @Ignore val password: String )
在这个例子中,我们使用 @Ignore
将 password
字段指定为应该被忽略的字段。这意味着在数据库表中,这个字段将不会出现,也不会被映射到 User
实体类中。
(1)忽略一个实体类中的 getter 和 setter 方法
除了忽略一个实体类中的字段外,@Ignore
注解还可以用于忽略一个实体类中的 getter 和 setter 方法。这可以帮助控制 Room 如何处理实体类中的方法。
例如,如果希望 Room 不生成一个实体类中的 setter 方法,则可以将 @Ignore
注解添加到该方法上:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, val password: String ) { @Ignore fun setPassword(password: String) { // ... } }
在这个例子中,我们使用 @Ignore
将 setPassword
方法指定为应该被忽略的方法。这意味着 Room 不会生成一个 setter 方法来设置 password
字段的值。
(2)忽略一个实体类中的整个构造函数
最后,@Ignore
注解还可以用于忽略一个实体类中的整个构造函数。这可以帮助控制 Room 如何处理实体类中的构造函数。
例如,如果希望 Room 不使用默认的构造函数来创建实体类的实例,则可以使用 @Ignore
注解标记该构造函数:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, val password: String ) { @Ignore constructor(id: Int, name: String) : this(id, name, "") }
在这个例子中,我们使用 @Ignore
将第二个构造函数指定为应该被忽略的构造函数。这意味着 Room 不会使用这个构造函数来创建 User
实体类的实例。
9、@Index
考验你数据库知识的时候来了!索引(个索引、多个索引、复合索引)可以提高数据库表查询的性能,因为它们使数据库系统能够更快地查找和排序表中的数据。
(1)在一个实体类中创建单个索引
@Index
注解最常用的用法是在一个实体类中创建单个索引。例如:
@Entity(tableName = "users", indices = [Index(value = ["name"])]) data class User( @PrimaryKey val id: Int, val name: String, val age: Int )
在这个例子中,我们使用 @Index
注解在 name
字段上创建了一个单个索引。这将使数据库系统能够更快地查找和排序 User
表中的数据。
(2)在一个实体类中创建多个索引
除了在一个实体类中创建单个索引外,@Index
注解还可以用于在一个实体类中创建多个索引。例如:
@Entity(tableName = "users", indices = [ Index(value = ["name"]), Index(value = ["age"]) ]) data class User( @PrimaryKey val id: Int, val name: String, val age: Int )
在这个例子中,我们使用 @Index
注解在 name
和 age
字段上创建了两个索引。这将使数据库系统能够更快地查找和排序 User
表中的数据。
(3)在一个实体类中创建复合索引
@Index
注解还可以用于在一个实体类中创建复合索引。复合索引是指将多个字段组合在一起以创建一个索引,这将使数据库系统能够更快地查找和排序这些字段的组合。
例如,如果希望在 User
表中按照 name
和 age
字段的组合进行排序,则可以使用 @Index
注解来创建一个复合索引:
@Entity(tableName = "users", indices = [ Index(value = ["name", "age"]) ]) data class User( @PrimaryKey val id: Int, val name: String, val age: Int )
在这个例子中,我们使用 @Index
注解在 name
和 age
字段上创建了一个复合索引。这将使数据库系统能够更快地查找和排序 User
表中按照 name
和 age
字段的组合进行排序的数据。
10、@Entity
当在 Room 中定义一个实体类时,必须使用 @Entity
注解来指定该类应该被映射到数据库中的哪个表。
(1)在一个实体类中指定表名
@Entity
注解最常用的用法是在一个实体类中指定表名。例如:
@Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, val age: Int )
在这个例子中,我们使用 @Entity
注解将 User
实体类映射到名为 users
的数据库表中。这将使 Room 能够将 User
类中的字段映射到数据库表中的相应列中。
(2)在一个实体类中指定索引
除了在一个实体类中指定表名外,@Entity
注解还可以用于在一个实体类中指定索引。索引可以提高数据库表查询的性能,因为它们使数据库系统能够更快地查找和排序表中的数据。
例如,如果希望在 User
表中按照 name
字段进行排序,则可以使用 @Entity
注解来创建一个索引:
@Entity(tableName = "users", indices = [Index(value = ["name"])]) data class User( @PrimaryKey val id: Int, val name: String, val age: Int )
在这个例子中,我们使用 @Entity
注解在 name
字段上创建了一个索引。这将使数据库系统能够更快地查找和排序 User
表中的数据。
(3)在一个实体类中指定继承关系
最后,@Entity
注解还可以用于在一个实体类中指定继承关系。如果的实体类继承自另一个实体类,则可以使用 @Entity
注解来指定它们之间的关系。
例如,如果有一个 Person
实体类和一个 Employee
实体类,Employee
实体类继承自 Person
实体类,则可以使用 @Entity
注解来指定它们之间的关系:
@Entity(tableName = "user") open class User( @PrimaryKey val id: Int, val name: String ) @Entity(tableName = "employees") data class Employee( @PrimaryKey val id: Int, val salary: Int, @ColumnInfo(name = "user_id") val userId: Int ) : User(userId, "")
在这个例子中,我们使用 @Entity
注解将 Person
实体类映射到名为 people
的数据库表中,并将 Employee
实体类映射到名为 employees
的数据库表中。此外,我们还使用 @Entity
注解指定了 Employee
实体类继承自 Person
实体类,并使用 @ColumnInfo
注解将 person_id
字段指定为 Employee
表中的外键。这将确保在插入或更新 Employee
表中的数据时,person_id
字段的值必须是 Person
表中存在的主键值之一。
11、@Dao
@Dao
是用于访问数据库中数据的一种抽象层。在 Room 中,每个 DAO 都定义了一组用于与数据库进行交互的方法。意思是就这么用,没啦。
@Dao interface UserDao {}
12、@Database
@Database
注解是 Room 中的一个注解,用于定义数据库类。当在 Room 中定义一个数据库时,必须使用 @Database
注解来指定该数据库包含哪些实体类和版本号等信息。
(1)在一个类中定义数据库实例
@Database
注解最常用的用法是在一个类中定义数据库实例。例如:
@Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
在这个例子中,我们使用 @Database
注解定义了一个数据库类 AppDatabase
,并在其中指定了包含 User
实体类的数据库版本号。此外,我们还定义了一个抽象方法 userDao()
,用于返回一个 UserDao
数据访问对象 (DAO)。
(2)指定多个实体类
@Database
注解还可以用于指定多个实体类。例如:
@Database(entities = [User::class, Address::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun addressDao(): AddressDao }
在这个例子中,我们使用 @Database
注解指定了包含 User
和 Address
实体类的数据库版本号。然后,我们定义了两个抽象方法 userDao()
和 addressDao()
,分别用于返回 UserDao
和 AddressDao
数据访问对象 (DAO)。
(3)指定数据库升级策略
最后,@Database
注解还可以用于指定数据库升级策略。当升级数据库时,可能需要指定一些操作来处理数据模式的变化。Room 提供了两种升级策略:Migrate
和 FallbackToDestructiveMigration
。
例如,如果希望在升级数据库时保留现有数据,可以使用 Migrate
升级策略:
val migration1to2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { // TODO: write migration code here } } @Database(entities = [User::class], version = 2, migrations = [migration1to2]) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao }
在这个例子中,我们使用 Migrate
升级策略将数据库版本从 1 升级到 2。我们定义了一个名为 migration1to2
的迁移对象,用于在升级数据库时执行自定义的 SQL 语句。然后,我们使用 @Database
注解指定了包含 User
实体类的数据库版本号和升级策略。
如果不需要保留现有数据,可以使用 FallbackToDestructiveMigration
升级策略:
@Database(entities = [User::class], version = 2, exportSchema = false) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao companion object { @Volatile private var instance: AppDatabase? = null fun getInstance(context: Context): AppDatabase { return instance ?: synchronized(this) { instance ?: buildDatabase(context).also { instance = it } } } private fun buildDatabase(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, "app-database") .fallbackToDestructiveMigration().build() } } }
在这个例子中,我们使用 FallbackToDestructiveMigration
升级策略将数据库版本从 1 升级到 2。我们使用 @Database
注解指定了包含 User
实体类的数据库版本号和升级策略,并将 exportSchema
参数设置为 false
,以避免生成不必要的 JSON
文件。
13、@Query
用于定义SQL查询语句,可以在之前的例子中找到,许多的@Query
的身影 (考验你数据库基础的时候到了!)
(1)基本查询操作
@Query
注解最常用的用法是执行基本的查询操作。例如:
@Dao interface UserDao { @Query("SELECT * FROM users") fun getAllUsers(): List<User> }
在这个例子中,我们使用 @Query
注解定义了一个基本的 SQL 查询语句,该语句将返回 users
表中的所有数据。我们将此查询定义为 getAllUsers()
方法的一部分,以便在需要时调用该方法。
(2)带参数的查询操作
@Query
注解还可以用于执行带参数的查询操作。例如:
@Dao interface UserDao { @Query("SELECT * FROM users WHERE id = :id") fun getUserById(id: Int): User }
在这个例子中,我们使用 @Query
注解定义了一个带有参数的 SQL 查询语句,该语句将返回 users
表中 id
字段等于给定值的数据。我们将此查询定义为 getUserById()
方法的一部分,并将 id
参数传递给查询语句。
(3)使用关联查询
最后,@Query
注解还可以用于执行关联查询。关联查询是一种可以跨多个表查询数据的查询类型。
@Dao interface UserDao { @Query("SELECT * FROM users INNER JOIN addresses ON users.address_id = addresses.id") fun getUsersWithAddresses(): List<UserWithAddress> }
在这个例子中,我们使用 @Query
注解定义了一个关联查询语句,该语句将返回 users
表中的数据以及与之关联的 addresses
表中的数据。我们将此查询定义为 getUsersWithAddresses()
方法的一部分,并使用 INNER JOIN
子句指定 users
表和 addresses
表之间的关系。
14、@Insert、@Update、@Delete
顾名思义哈,也就不用举例了,嘻嘻嘻
@Dao interface UserDao { @Insert\@Update\@Delete fun xxxUser(user: User) }
15、多数据源
使用Kotlin Flow可以很方便地处理多个数据源的情况。在使用Room时,我们可以在Repository层中实现本地和远程数据源的逻辑,并使用Kotlin Flow来组合和转换数据。
以下是一个示例,演示了如何使用Room和Kotlin Flow处理多个数据源的情况:
class UserRepository( private val userDao: UserDao, private val api: ApiService ) { fun getUsers(): Flow<List<User>> { val localUsers = userDao.getAll().asFlow() val remoteUsers = api.getUsers().asFlow() return localUsers.combine(remoteUsers) { local, remote -> // 合并本地和远程数据 (local + remote).distinctBy { it.id } } } suspend fun updateUser(user: User) { api.updateUser(user.id, user) userDao.update(user) } }
在以上示例中,我们在UserRepository中使用了本地和远程数据源,并使用Kotlin Flow.combine操作符将本地和远程数据源合并在一起,并在最后返回一个Flow对象。我们还使用了suspend修饰符将updateUser方法标记为挂起函数,以便可以在协程中执行异步操作。
很方便吧
16、@Fts3和@Fts4
这个一般用不到来着,不过如果你要做小说软件的话,可能有用。用于创建全文本搜索虚拟表。全文本搜索是一种在大型文本数据集中搜索特定文本片段的技术。当您需要在应用程序中实现全文本搜索时,可以使用这两个注解来创建虚拟表。
(1)@Fts3 注解
@Fts3
注解用于创建一个基于 SQLite FTS3 算法的虚拟表。例如:
@Fts3 @Entity(tableName = "books") data class Book( @ColumnInfo(name = "book_title") val title: String, @ColumnInfo(name = "book_author") val author: String, @ColumnInfo(name = "book_description") val description: String )
在这个例子中,我们使用 @Fts3
注解定义了一个名为 books
的虚拟表。该表将基于 title
、author
和 description
列的内容创建一个全文本索引。当您执行全文本搜索时,将使用该索引来查找与搜索查询匹配的行。
(2)@Fts4 注解
@Fts4
注解用于创建一个基于 SQLite FTS4 算法的虚拟表。例如:
@Fts4(contentEntity = Book::class) @Entity(tableName = "book_fts") data class BookFts( @ColumnInfo(name = "book_fts_title") val title: String, @ColumnInfo(name = "book_fts_author") val author: String, @ColumnInfo(name = "book_fts_description") val description: String )
在这个例子中,我们使用 @Fts4
注解定义了一个名为 book_fts
的虚拟表。该表将基于 title
、author
和 description
列的内容创建一个全文本索引。与 @Fts3
注解不同的是,@Fts4
注解需要使用 contentEntity
参数指定要创建索引的实体类。
(3)使用全文本搜索
创建全文本搜索虚拟表后,您可以使用 Room 中的 MATCH
关键字来执行全文本搜索。例如:
@Dao interface BookDao { @Query("SELECT * FROM books WHERE books MATCH :query") fun searchBooks(query: String): List<Book> }
在这个例子中,我们使用 MATCH
关键字来执行全文本搜索操作。该操作将在 books
虚拟表中搜索与 query
参数匹配的行,并返回所有匹配的结果。注意,在使用这些注解时,请确保为要搜索的列创建了索引,以避免搜索操作变得缓慢或不可用。
总结
我们在这一篇提到了17个注释,,我们可以使用以上注解来定义实体类、DAO接口和数据库,并实现各种数据操作和类型转换。这些注解可以帮助我们更好地使用Room进行开发,并实现更加复杂和高效的功能,涵盖了绝大数开发的需求。当然,在实际开发中,我们还需要根据具体的业务需求和技术特点来选择合适的方案和实现方式。
实际上,Room的应用远不止如此,如果有人感兴趣的话,我就继续出下一期吧!
感谢
- 校稿:ChatGpt/Bing
- 文笔优化:ChatGpt/Bing/秘塔写作猫
以上就是快速了解Android Room使用细则进阶的详细内容,更多关于Android Room使用细则的资料请关注脚本之家其它相关文章!