Jetpack Room初次尝试,简直好用到飞起

3,291 阅读4分钟

Jetpack Room 初次尝试

Jetpack Room是Android官方提供的一个操作App SQLite数据库的框架。整体使用起来比较简单。

写了个简单的管理用户的Hello World。

202107222304.gif

环境

Android Studio 4.2
Kotlin 1.4.31
Kotlin Coroutines 1.5.0
Jetpack Room 2.3.0 官网传送门

  • 依赖添加
def room_version = "2.3.0"

// 核心库
implementation "androidx.room:room-runtime:$room_version"
// 使用kapt(Kotlin annotation processing tool)处理注解
kapt "androidx.room:room-compiler:$room_version"
// 使用ksp(Kotlin Symbolic Processing)处理注解(速度会更快一点)
ksp("androidx.room:room-compiler:$room_version")

// Room协程支持
implementation("androidx.room:room-ktx:$room_version")

// Room RxJava2 支持
implementation "androidx.room:room-rxjava2:$room_version"

// Room RxJava3 支持
implementation "androidx.room:room-rxjava3:$room_version"

// Room Guava支持
implementation "androidx.room:room-guava:$room_version"

// Room测试
testImplementation("androidx.room:room-testing:$room_version")

// 协程
def coroutine_version = "1.5.0"
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
// 提供Android专属调度器
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"

为啥要导入协程库呢?因为Room规定数据库操作不能在主线程上执行,因此这里引入了协程来处理这个问题,另外,Room是支持协程的,可以和协程结合起来使用。

Room三大组件

Room 包含三大组件:

  • DataBase 数据库持有类,继承自RoomDatabase类,持有0个或多个获取DAO的抽象方法
  • Entity 数据实体类,定义了数据库表的结构。
  • DAO 数据操作接口,定义的操作数据的方法。

Hello World

来看看怎样写一个Hello World。

首先肯定得定义一个实体,就叫他User吧。

// 使用@Entity注解,标识这是一个Entity组件
@Entity
data class User(
    // 主键,autoGenerate=true自动生成
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    // 列信息
    @ColumnInfo(name = "name")
    val name: String="",
    @ColumnInfo(name = "gender")
    val gender: String="",
    @ColumnInfo(name = "age")
    val age: String=""
)

有了实体,肯定得有操作实体的方法吧,再定义一个UserDao

// 标识这是一个DAO组件
@Dao
interface UserDao {
    // 插入用户的注解,vararg表示可以一次插入多个用户
    // onConflict标识插入遇到冲突时的解决策略,可选REPLACE,ROLLBACK,ABORD,FAIL,IGNORE,简单明了,看单词就能明白作用
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUser(vararg users: User)
    
    // 删除用户的注解
    @Delete
    fun deleteUser(user: User)

    // 删除所有用户的注解,使用Query注解可以执行自定义的SQL语句
    @Query("DELETE FROM user")
    fun deleteAll()
    
    // 更新用户的注解,同样可以设置冲突策略
    @Update(onConflict = OnConflictStrategy.REPLACE)
    fun updateUser(user: User)
    
    // 查询所有用户
    @Query("SELECT * FROM user")
    fun queryAllUser(): List<User>

    // 根据名字查询用户,:name对应方法中的name参数
    @Query("SELECT * FROM USER WHERE name=:name")
    fun queryByName(name: String): User?
}

有了DAO,怎么使用DAO中的方法来操作数据库呢,还需要定义一个UserDataBase抽象类

// @DataBase标识这是一个Database组件,entities表示DataBase拥有的实体,可以传入多个,version用于数据库版本更新,这里填1,当然@Database还可以设置其它的参数,这里就不详解了
@Database(entities =[User::class,],version = 1)
abstract class UserDataBase :RoomDatabase(){
    // 定义获取上面定义的UserDao的抽象方法
    abstract fun userDao():UserDao
}

好了三大组件已经集齐,接下来就该实际上手操作了。

首先需要使用Room构造一个UserDataBase对象

private val dataBase: UserDataBase by lazy {
    // 传入context,定义的UserDataBase以及数据库文件名,databaseBuilder还可以设置其它的一些内容,如从文件导入数据库,超时时间等,这里不做介绍。
    Room.databaseBuilder(this, UserDataBase::class.java, "user.db").build()
}

因为Hell World只有在MainActivity中使用,因此就直接在MainActivity中使用by lazy创建,正常情况下请使用单例懒汉模式创建,因为频繁的创建DataBase开销是很高的。

有了dataBase对象,就可以直接获取到UserDao来操作数据了,从最简单的增删改查开始吧。
首先写一个创造用户的方法:

private fun createUser(): User {
    val name = binding.etName.text.toString()
    val gender = binding.etGender.text.toString()
    val age = binding.etAge.text.toString()
    return User(name = name, gender = gender, age = age)
}

很简单,就是读取输入框中的数据,构造一个用户,这里使用了ViewBinding来获取组件。

binding.btnInsert.setOnClickListener {
    launch {
        // Room操作数据库不能在主线程,因此这里使用IO线程操作
        withContext(Dispatchers.IO) {
            dataBase.userDao().insertUser(createUser())
        }
        // 插入后更新UI
        updateView()
        Toast.makeText(this@MainActivity, "Insert success", Toast.LENGTH_SHORT).show()
    }
}
binding.btnDelete.setOnClickListener {
    launch {
        withContext(Dispatchers.IO) {
            dataBase.userDao().deleteUser(createUser().copy(id=getUid()))
        }
        updateView()
        Toast.makeText(this@MainActivity, "Delete success", Toast.LENGTH_SHORT).show()
    }
}
  • 删除全部
binding.btnDelete.setOnLongClickListener {
    launch {
        withContext(Dispatchers.IO) {
            dataBase.userDao().deleteAll()
        }
        updateView()
        Toast.makeText(this@MainActivity, "Delete all success", Toast.LENGTH_SHORT).show()
    }
    true
}
// 获取输入框中的UID
private fun getUid(): Long {
    return try {
        binding.etId.text.toString().toLong()
    } catch (e: Exception) {
        0L
    }
}

binding.btnUpdate.setOnClickListener {
    launch {
        // 这里使用copy方法用构造的User创建一个新的User,并设置id
        withContext(Dispatchers.IO) {
            dataBase.userDao().updateUser(createUser().copy(id=getUid()))
        }
        updateView()
        Toast.makeText(this@MainActivity, "Update success", Toast.LENGTH_SHORT).show()
    }
}
binding.btnQuery.setOnClickListener {
    launch {
        // 根据名字查询用户,withContext会返回最后一行的内容,因此这里能够拿到用户信息
        val user = withContext(Dispatchers.IO) {
            dataBase.userDao().queryByName(binding.etName.text.toString())
        }
        user?.let {
            binding.tvInfo.text = "${it.id}-${it.name} ${it.gender} ${it.age}"
        }
        Toast.makeText(this@MainActivity, "Query success", Toast.LENGTH_SHORT).show()
    }
}

怎么样,是不是超级简单,Jetpack出的一些框架真的是懒人的福利啊。
最后放一下整体源码

  • MainACtivity
package com.ricky.room

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.room.Room
import com.ricky.room.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import java.lang.Exception

class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
    private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
    private val dataBase: UserDataBase by lazy {
        Room.databaseBuilder(this, UserDataBase::class.java, "user.db").build()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.btnInsert.setOnClickListener {
            launch {
                withContext(Dispatchers.IO) {
                    dataBase.userDao().insertUser(createUser())
                }
                updateView()
                Toast.makeText(this@MainActivity, "Insert success", Toast.LENGTH_SHORT).show()
            }
        }
        binding.btnDelete.setOnClickListener {
            launch {
                withContext(Dispatchers.IO) {
                    dataBase.userDao().deleteUser(createUser().copy(id=getUid()))
                }
                updateView()
                Toast.makeText(this@MainActivity, "Delete success", Toast.LENGTH_SHORT).show()
            }
        }
        binding.btnDelete.setOnLongClickListener {
            launch {
                withContext(Dispatchers.IO) {
                    dataBase.userDao().deleteAll()
                }
                updateView()
                Toast.makeText(this@MainActivity, "Delete all success", Toast.LENGTH_SHORT).show()
            }
            true
        }
        binding.btnUpdate.setOnClickListener {
            launch {
                withContext(Dispatchers.IO) {
                    dataBase.userDao().updateUser(createUser().copy(id=getUid()))
                }
                updateView()
                Toast.makeText(this@MainActivity, "Update success", Toast.LENGTH_SHORT).show()
            }
        }
        binding.btnQuery.setOnClickListener {
            launch {
                val user = withContext(Dispatchers.IO) {
                    dataBase.userDao().queryByName(binding.etName.text.toString())
                }
                user?.let {
                    binding.tvInfo.text = "${it.id}-${it.name} ${it.gender} ${it.age}"
                }
                Toast.makeText(this@MainActivity, "Query success", Toast.LENGTH_SHORT).show()
            }
        }
        updateView()
    }

    private fun getUid(): Long {
        return try {
            binding.etId.text.toString().toLong()
        } catch (e: Exception) {
            0L
        }
    }

    private fun createUser(): User {
        val name = binding.etName.text.toString()
        val gender = binding.etGender.text.toString()
        val age = binding.etAge.text.toString()
        return User(name = name, gender = gender, age = age)
    }

    private fun updateView() {
        launch {
            val text = withContext(Dispatchers.IO) {
                val users = dataBase.userDao().queryAllUser()
                users.joinToString("\n") { "${it.id}-${it.name} ${it.gender} ${it.age}" }
            }
            binding.tvInfo.text = text
        }
    }
}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@+id/guideline2"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="435dp" />

    <EditText
        android:id="@+id/et_id"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="10dp"
        android:hint="ID"
        app:layout_constraintEnd_toStartOf="@id/et_name"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <EditText
        android:id="@+id/et_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="10dp"
        android:hint="姓名"
        app:layout_constraintEnd_toStartOf="@id/et_gender"
        app:layout_constraintStart_toEndOf="@id/et_id"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <EditText
        android:id="@+id/et_gender"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="10dp"
        android:hint="性别"
        app:layout_constraintEnd_toStartOf="@id/et_age"
        app:layout_constraintStart_toEndOf="@id/et_name"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <EditText
        android:id="@+id/et_age"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="10dp"
        android:hint="年龄"
        android:inputType="number"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/et_gender"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <Button
        android:id="@+id/btn_insert"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="insert"
        app:layout_constraintBottom_toTopOf="@+id/btn_update"
        app:layout_constraintEnd_toStartOf="@id/btn_delete"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guideline2" />

    <Button
        android:id="@+id/btn_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="delete"
        app:layout_constraintBottom_toTopOf="@id/btn_query"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/btn_insert"
        app:layout_constraintTop_toBottomOf="@id/guideline2" />

    <Button
        android:id="@+id/btn_update"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="14dp"
        android:text="update"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btn_query"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/btn_insert" />

    <Button
        android:id="@+id/btn_query"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="query"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/btn_update"
        app:layout_constraintTop_toBottomOf="@id/btn_delete" />
</androidx.constraintlayout.widget.ConstraintLayout>