android Room的使用
老规矩,在做完一个项目之后需要对开发过程中使用到的新知识点做一个总结,最近使用kt完成了一个纯上层的app, 其中对数据库的操作使用Room框架来完成,大大节省了工作量,可以说用的很爽,因此本节就这个知识点来进行一系列的总结,以便填充本人对整个Jetpack库知识的积累。
- 概念
 - 集成
 - 应用
 - 特性
 

概念
Room是jetpack开发包中的一个基于SQLite开发的一个数据库操作抽象层框架,官方推出的这个框架能够针对sql命令在工程编译时进行校验,且简化了数据库的迁移工作,最大限度的减少了数据库操作的样板代码等。
集成
工程的gradle文件(最外层的build.gradle)中添加依赖:
   dependencies {
        ......
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
   }
 
模块的gradle文件(app中的build.gradle或者其他module中的gradle)配置如下:
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'kotlin-android-extensions'
}
android {
   //如果不需要导出数据库的schema文件则无需配置此项
   kapt {
       arguments {
          arg("room.schemaLocation", "$projectDir/schemas")
       }
   }
}
dependencies {
    kapt "androidx.room:room-compiler:2.4.2"
    implementation "androidx.room:room-runtime:2.4.2"
    implementation "androidx.room:room-ktx:2.4.2"
}
 
应用
app中应用Room主要分为三个模块:数据库类,数据操作类,数据实体类。按照依赖顺序介绍,先从数据实体类开始介绍
数据实体类
- @Entity
 
- @PrimaryKey
 
- @ColumnInfo
 
- @Ignore
 
举例如下,创建了student_table与ClassN两个表
//创建数据表为stub_table
@Entity(tableName = "student_table")
data class Student(@PrimaryKey val id:Int,
                   @ColumnInfo(name="custom_name", typeAffinity = ColumnInfo.TEXT) val name:String,
                   @ColumnInfo val age:Int,
                   @ColumnInfo val classId:Int)
@Entity
data class ClassN(@PrimaryKey val classId:Int,
                  @ColumnInfo val className:String)
 
数据操作类(Dao)
Dao 必须是接口或者抽象类,因为 Room 在编译期间生成他们的实现类.
- @Dao
 
- @Insert
 
- @Update
 
- @Delete
 
- @Query
 
相关实现如下:
@Dao
interface SchoolDao {
    //插入N条ClassN数据
    @Insert
    fun insertClass(vararg classN: ClassN)
	//查询数据表中的所有行,并将其转为ClassN的集合
    @Query("SELECT * FROM ClassN")
    fun queryClass() : List<ClassN>
    //查询所有classId在ids数组中的行
    @Query("SELECT * FROM user WHERE classId IN (:ids)")
	fun queryClass(ids: IntArray) : List<ClassN>
    //删除对应的ClassN对象
    @Delete
    fun deleteClass(cn: ClassN)
	//删除指定classId = cid的行
    @Query("DELETE FROM ClassN where classId = :classId")
    fun deleteClass(cid : Int)
    //更新表中的ClassN
    @Update
    fun updateClass(cn:ClassN)
}
 
数据库创建类
数据库访问与实体都创建好了之后,我们需要创建一个数据库,然后通过该类对外提供各类Dao的获取接口, 实现步骤如下:
- 定义抽象类继承自RoomDatabase
 - 使用@DataBase注解类
 
- 定义各种Dao类的获取方法
 
abstract fun schoolDao() : SchoolDao
abstract fun studentDao() : StudentDao
 
- 使用Room类的databaseBuilder来构造数据库
 
Room.databaseBuilder(ctx, AppDatabase::class.java, "test_db")
    .addCallback(object : RoomDatabase.Callback(){
         override fun onCreate(db: SupportSQLiteDatabase) {
             super.onCreate(db)
                 Log.i("debug", "----------> db create")
         }
     })
    .build()
 
完整代码如下:
@Database(version=1, entities = [Student::class, ClassN::class])
abstract class AppDatabase : RoomDatabase() {
     abstract fun schoolDao() : SchoolDao
     abstract fun studentDao() : StudentDao
     companion object {
          @Volatile
          private var instance : AppDatabase ?= null
          fun instance(ctx:Context) : AppDatabase{
               return instance ?: synchronized(this){
                    instance ?: buildDataBase(ctx).also {
                         instance = it
                    }
               }
          }
          private fun buildDataBase(ctx:Context) : AppDatabase{
               return Room.databaseBuilder(ctx, AppDatabase::class.java, "test_db")
                    .addCallback(object : RoomDatabase.Callback(){
                         override fun onCreate(db: SupportSQLiteDatabase) {
                              super.onCreate(db)
                              Log.i("debug", "----------> db create")
                         }
                    })
                    .build()
          }
     }
}
 
其他特性
Android Room可以直接返回数据的数据类型以外,还可以直接以异步流(Flow)的方式返回,如下:
@Query("select * from ClassN")
fun queryClassByFlow() : Flow<List<ClassN>>
CoroutineScope(Dispatchers.Main).launch {
    AppDatabase.instance(baseContext)
    		   	   .schoolDao()
    			   .queryClassByFlow()
              	   .map {
                        var str = ""
                        it.forEach {
                            str = "${str}-${it.classId}-${it.className}"
                        }
                        str
                    }
                    .flowOn(Dispatchers.IO)
                    .collectLatest { text-> 
                        content.text = text
                    }
}
 
通过结合异步流,我们可以动态监听数据表中的某个数据变化;使用Flow的distinctUntilChanged()方法可以让我们在数据变化后得到通知,如下:
Dao中的定义
@Query("select * from ClassN where classId=:classId")
fun queryClassByFlow(classId:Int) : Flow<ClassN>
fun queryClassDistinct(classId : Int) = queryClassByFlow(classId).distinctUntilChanged()
界面中监听数据
CoroutineScope(Dispatchers.Main).launch {
            AppDatabase.instance(baseContext).schoolDao().queryClassDistinct(2)
                .flowOn(Dispatchers.IO)
                .collectLatest {
                    content.text = it.className
            }
}
这样每次classId为2的数据变化后,我们在collectLatest中都能收到通知
 
官方文档










