• Android Jetpack系列(七):Room(使用篇)


    前言

    前面几篇讲解了Lifecycle,LiveData,ViewModel,有了前面这几篇的铺垫,就能引出我们今天要讲解的 Room 了, Room是一个数据库访问组件; 对SqLite数据库做了友好的封装,使我们在编码的时候,只需要注重逻辑的部分即可,数据库就交给Room去流畅的访问即可

    Room使用步骤

    1 添加依赖

    build.gradle {
    apply plugin: 'kotlin-kapt'
    
    dependencies {
    	kapt "androidx.room:room-compiler:$rootProject.roomVersion"
    	implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2 创建Entity实体类

    @Entity(tableName = "apps")
    data class AppEntity(
            @ColumnInfo(name = "packageName") @PrimaryKey val packageName: String,
            @ColumnInfo(name = "app_id") val id: Int,
            @ColumnInfo(name = "versionCode") val versionCode: String,
            @ColumnInfo(name = "versionLabel") val versionLabel: String,
            @ColumnInfo(name = "versionName") val versionName: String,
            @ColumnInfo(name = "description") val description: String,
            @ColumnInfo(name = "icon") val icon: String)
     
     
     @Entity(tableName = "comments",
            foreignKeys = [
                ForeignKey(entity = AppEntity::class,
                        parentColumns = ["packageName"],
                        childColumns = ["packageName"],
                        onDelete = ForeignKey.CASCADE)
            ],
        	indices = [Index("packageName")])
    class CommentEntity(@PrimaryKey(autoGenerate = true) val id: Int = 0,
                    val packageName: String,
                    val comment: String = "this is comment for $packageName")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    实体类我们采用的是注解Entity来标记,其中有很多属性:

    • 1 tableName: 用来设置数据库中表名字。如果不设置这个值的话,默认是类的名字

    • 2 indices: 用来设置索引
      索引用于提高数据库表的数据访问速度的,有单列索引和组合索引

    • 3 inheritSuperIndices: 父类的索引是否会自动被当前类继承

    • 4 primaryKeys: 用来设置主键,如果这个主键的值可以唯一确定这个对象,就可以只设置一个主键。如果一个字段值不能够唯一确定对象,就需要复合主键,这里primaryKeys可以设置数组;另外每一个Entity都需要设置一个主键,如果父类和子类都设置了主键,则子类的主键会覆盖父类的主键

    • 5 foreignKeys: 用来设置外键,也就是FOREIGN KEY约束。因为Sqlite数据库属于关系型数据库,所以表于表之间会有关系存在,那么这个属性值就用来联系两个表单之间的关系;如上CommentEntity中设置了外键为AppEntity中的packageName

    • 6 ignoredColumns: 被忽略的字段

    类中还使用了ColumnInfo注解; 其中的属性值name用来标记的是表中一个字段在数据库中的存储的字段值,如果不设置的话默认为声明的字段的值

    另外还有Embedded,表示的是嵌套对象; 我们可以把类A放入另外一个类B中,只需要在B中对A使用注解Embedded即可,这样的话,B就可以正常使用A中所有的属性值

    3 声明Dao对象

    @Dao
    interface AppsDao {
    
        @Query("SELECT * FROM apps")
        fun loadApps(): LiveData<List<AppEntity>>
    
        @Query("SELECT * FROM apps WHERE packageName = :packageName")
        fun loadApp(packageName: String): LiveData<AppEntity>
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insertAll(apps: List<AppEntity>)
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun insert(app: AppEntity)
    
        @Delete
        fun delete(app: AppEntity)
    
        @Update
        fun update(app: AppEntity)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这个Dao对象的声明必须使用interface修饰; 另外我们看到提供了四种增删改查的注解,只有查询的注解需要输入少量的SQL语句,定义接口的返回值还可以是LiveData等可观察的数据,操作起来是非常方便的

    当我们同步代码之后会在generated中生成一个xxx_Impl.java对象,里面将我们声明的接口方法都做了实现,不需要我们自己处理了

    4 声明Database对象

    @Database(entities = [AppEntity::class, CommentEntity::class], version = 1, exportSchema = false)
    abstract class AppDatabase : RoomDatabase() {
    
        abstract fun appsDao(): AppsDao
    
        abstract fun commentsDao(): CommentsDao
    
        companion object {
            private const val DATABASE_NAME = "forward-db"
            private val executors: ExecutorService = Executors.newSingleThreadExecutor()
            @Volatile
            private var instance: AppDatabase? = null
    
            fun getInstance(context: Context): AppDatabase {
                return instance ?: synchronized(this) {
                    instance ?: buildDatabase(context.applicationContext).also {
                        instance = it
                    }
                }
            }
    
            private fun buildDatabase(context: Context): AppDatabase {
                return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
                        .addCallback(object : Callback() {
                            override fun onCreate(db: SupportSQLiteDatabase) {
                                executors.execute {
                                    Thread.sleep(3000)
                                    val request: OneTimeWorkRequest = OneTimeWorkRequestBuilder<AppsWorker>().build()
                                    WorkManager.getInstance(context).enqueue(request)
                                }
                            }
                        })
                        .build()
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    使用Database注解需要传入我们声明的所有的Entity对象,版本号version,以及是否导出Schema等属性值

    这个类是要继承RoomDatabase的,一般将这个类使用单例的形式提供使用; 并且采用建造者模式创建对象,我们可以将数据的获取放在某一个地方,这里是放在了数据库的onCreate方法中,这里采用的是WorkManager的方式,如下所示

    5 获取数据

    class AppsWorker(context: Context, workerParameters: WorkerParameters)
        : CoroutineWorker(context, workerParameters) {
    
        private val TAG by lazy {
            AppsWorker::class.java.simpleName
        }
    
        override suspend fun doWork(): Result = coroutineScope {
            try {
                applicationContext.assets.open("apps.json").use {
                    JsonReader(it.reader()).use { reader ->
                        val appsType = object : TypeToken<List<AppEntity>>() {}.type
                        val appsList: List<AppEntity> = Gson().fromJson(reader, appsType)
                        val comments = DataGenerator.getComments(appsList)
                        val appsDao = RepositoryProvider.providerAppsRepository(applicationContext)
                        val commentDao = RepositoryProvider.providerCommentsRepository(applicationContext)
                        appsDao.insertAll(appsList)
                        commentDao.insertAll(comments)
                    }
                    Result.success()
                }
            } catch (e: Exception) {
                Result.failure()
            }
        }
    
        private fun insertData(database: AppDatabase, apps: List<AppEntity>, comments: List<CommentEntity>) {
            database.runInTransaction {
                database.appsDao().insertAll(apps)
                database.commentsDao().insertAll(comments)
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    WorkManager的使用不是这一节的重点,它的使用比较简单,但是源码分析却是比较复杂的;后面会单独的进行讲解

    6 最终使用

       viewModel.apps.observe(viewLifecycleOwner, Observer {
            if (it.isNullOrEmpty()) {
                binding.loading = true
            } else {
                binding.loading = false
                adapter.setList(it)
            }
            binding.executePendingBindings()
        })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 调用了上述的代码就将我们的数据和生命周期仅仅绑定在一起,并且如果数据发生变化的话,会立刻回调我们更新UI的代码,就达到了我们的目的

    有需要文章中完整代码的同学 现在私信发送 “底层源码” 即可免费获取

    最后我想说:

    学习没有捷径可言,我们要注意记学习,不仅要记,还要写心得体会,文字笔记、画图、总结等,方式很多,但是一定要自己认真去做,不要太相信自己的记忆,只有反复记忆,加深理解才行

    同时,对于程序员而言,不单单是死记硬背,我们有更好的方式去学习,比如写demo去验证。复习知识点时,要及时跟你做过的项目结合起来,这样在面试时就知道怎么聊了,由项目讲到知识点,由一个知识点串联到另一个知识点。复习到一定阶段,可以尝试着去把这些东西串联起来,由点及面,形成知识体系

    对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们

    技术是无止境的,你需要对自己提交的每一行代码、使用的每一个工具负责,不断挖掘其底层原理,才能使自己的技术升华到更高的层面

    Android 架构师之路还很漫长,与君共勉

  • 相关阅读:
    开源LC3编解码器测试Demo
    Awesome-Selfhosted:互联网常见服务开源平替 | 开源日报 No.68
    zabbix 7.0 SNMP Hex数据预处理新功能
    提升复杂场景三维重建精度 | 基于PaddleSeg分割无人机遥感影像
    【番杰的小技巧笔记】如何通过嘉立创免费3D打印
    1053 Path of Equal Weight
    mysql学习使用
    Arrays类
    【Docker安装部署DockerCompose&案例演示&配置文件&命令实操学习】
    【计算机网络实验】虚拟局域网组建
  • 原文地址:https://blog.csdn.net/m0_70748845/article/details/126158364