Jetpack从入门到几乎入门(四)

1,022 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,[点击查看活动详情]

Jetpack从入门到几乎入门(四)

(juejin.cn/post/714765… "juejin.cn/post/714765…")

前言

Jetpack系列:

Jetpack从入门到几乎入门(一) - 掘金 (juejin.cn)

Jetpack从入门到几乎入门(二) - 掘金 (juejin.cn)

Jetpack从入门到几乎入门(三) - 掘金 (juejin.cn)

本文是我在学习guolin大神的《第一行代码》第三版Jetpack部分的Room的知识总结,文中部分代码参考自《第一行代码》第三版

在阅读本文前,您需要掌握kotlin语言的基本语法

Room简介

在学习Room之前,每当提起数据库操作,我们总是会想到SQLite。SQLite虽然简单易用,但是如果放到大型项目中却会让项目的代码变得混乱。而Room是基于SQLite上提供的一个抽象层,它不仅支持SQL语句,而且还有很多便捷的功能:

  • 具备SQL语句高亮和编译期检查
  • 通过简单的注解完成接口的定义,易上手
  • 支持协程、RxJava
  • 可以搭配AndroidStudio自带的数据库查看窗口使用

Room的增删改查操作

使用Room前要在build.gradle上添加依赖

plugins {
    ...
    id 'kotlin-kapt'
}
​
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
kapt 'androidx.room:room-compiler:$room_version'

我们先简单了解一下Room的三个组成部分

  • Entity :用于定义封装实际数据的实体类,每个实体类都在数据库中有一张对应的自动生成的表

  • Dao :Dao 指数据访问对象,对数据库各项操作的封装在此处进行,在实际编程中,逻辑层就不需要和底层数据库打交道,而是接和Dao 层进行交互

  • Database :用于定义数据库中的关键信息,包括数据库的版本号、包含哪些实体类以及提

    供Dao 层的访问实例。

接下来我们创建一个实体类:

@Entity
data class User(var firstName: String, var lastName: String, var age: Int){
    @PrimaryKey(autoGenerate = true)
    var id:Long = 0
}

在类名上使用@Entity注解,然后加入id字段,使用@PrimaryKey注解将它设为主键。autoGenerate的值设为true,主键的值为自动生成。

接下来开始定义Dao,在这里封装访问数据库的操作(增删改查)。

新建一个UserDao接口

@Dao
interface UserDao {
    @Insert
    fun insertUser(user: User): Long
​
    @Update
    fun updateUser(newUser: User)
​
    @Query("select * from User")
    fun loadAllUsers(): List<User>
​
    @Query("select * from User where age > :age")
    fun loadUsersOlderThan(age: Int): List<User>
​
     @Delete
    fun deleteUser(user: User)
​
    @Query("delete from User where lastName = :lastName")
    fun deleteUserByLastName(lastName: String): Int
}

首先,我们要在接口前用上@Dao注解,接着我们进行对增删改查操作进行封装。Room为我们提供了相应的注解@Insert、@Delete、@Update和@Query。

唯一需要写SQL语句的地方是在查询操作,我们必须告知Room需要查询什么地方,其他时候我们直接使用注解标识即可。这样我们就大体定义了添加用户、修改用户数据、查询用户、删除用户这几种数据库操作接口。

最后一个环节是定义Database,新建一个AppDatabase.kt 文件

@Database(version = 1, entities = [User::class])   //声明数据库的版本号,包含哪些实体类,多个实体类之间可用逗号隔开
abstract class AppDatabase : RoomDatabase(){
    abstract fun userDao(): UserDao //提供抽象方法,用于获取已编写的Dao实例;只声明方法,具体实现在Room底层自动完成
    companion object {  //Kotlin的类中不允许你声明静态成员或方法,要添加Companion对象来包装这些静态引用
        private var instance: AppDatabase? = null
        @Synchronized
        fun getDatabase(context: Context) : AppDatabase {
            instance?.let {
                return it //instance变量不为空直接返回
            }
            //否则就调用Room.databaseBuilder()方法来构建一个AppDatabase的实例
            return Room.databaseBuilder(context.applicationContext,
                AppDatabase::class.java, "app_database")
                .build().apply {
                    instance = this
                }
        }
    }
}

接下来我们修改页面,加上增删改查的4个按钮

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    ...
    <Button
        android:id="@+id/getUserBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Get User"/>
    <Button
        android:id="@+id/addDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Add Data"/>
    <Button
        android:id="@+id/updateDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Update Data"/>
    <Button
        android:id="@+id/deleteDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Delete Data"/>
    <Button
        android:id="@+id/queryDataBtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Query Data"/>
</LinearLayout>

在MainActivity中实现代码的逻辑

class MainActivity : AppCompatActivity() {
    val userDao = AppDatabase.getDatabase(this).userDao()
    val user1 = User("Tom", "Brady", 40)
    val user2 = User("Tom", "Hanks", 63)
    val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
​
        setContentView(binding.root)
        ...
        binding.addDataBtn.setOnClickListener {
            thread {
                user1.id = userDao.insertUser(user1)
                user2.id = userDao.insertUser(user2) //更新ID
            }
        }
        binding.updateDataBtn.setOnClickListener {
            thread {
                user1.age = 42
                userDao.updateUser(user1)
            }
        }
        binding.deleteDataBtn.setOnClickListener {
            thread {
                userDao.deleteUserByLastName("Hanks")
            }
        }
        binding.queryDataBtn.setOnClickListener {
            thread {
                for (user in userDao.loadAllUsers()) {
                    Log.d("MainActivity", user.toString())
                }
            }
        }
        
​
    ...
}

值得注意的是,数据库操作是耗时操作,所以Room默认不能在主线程中操作,但是Room也提供了方法允许我们在主线程进行操作:

Room.databaseBuilder(context.applicationContext, AppDatabase::class.java,"app_database")
 .allowMainThreadQueries() 
 .build()

不过此方法只建议在测试环境下使用。

Room的数据库更新

进行数据库升降级时,我们可以使用addMigration进行操作,在AppDatabase的getDatabase中编写instance:

instance = Room.databaseBuilder(
            context.applicationContext,
            AppDatabase::class.java,
            "user.db"
        ).addMigrations(object :Migration(1,2){ //从版本1到版本2,降级则倒转
            override fun migrate(database: SupportSQLiteDatabase) {
               database.execSQL("ALTER TABLE user ADD age INTEGER Default 0 not null ")
            }
​
        })allowMainThreadQueries().build()

Room还提供了两个方法:

  • fallbackToDestructiveMigration:升级时可使用该方法,当未匹配到版本的时候就会直接删除表然后重新创建。
  • fallbackToDestructiveMigrationOnDowngrade:降级时可添加此方法,当未匹配到版本的时候就会直接删除表然后重新创建。

总结

学会