Android Jetpack架构组件(七)Room使用篇

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

Room使用步骤 > Github项目地址

  • 1 添加依赖
    build.gradle {
    apply plugin: 'kotlin-kapt'
    
    dependencies {
    	kapt "androidx.room:room-compiler:$rootProject.roomVersion"
    	implementation "androidx.room:room-runtime:$rootProject.roomVersion"
    	}
    }
    
  • 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")
    
    • 实体类我们采用的是注解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)
    }
    
    • 这个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()
            }
        }
    }
    
    • 使用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)
            }
        }
    }
    
    • WorkManager的使用不是这一节的重点,它的使用比较简单,但是源码分析却是比较复杂的。后面会单独的进行讲解。
  • 6 最终使用
       viewModel.apps.observe(viewLifecycleOwner, Observer {
            if (it.isNullOrEmpty()) {
                binding.loading = true
            } else {
                binding.loading = false
                adapter.setList(it)
            }
            binding.executePendingBindings()
        })
    
    • 调用了上述的代码就将我们的数据和生命周期仅仅绑定在一起,并且如果数据发生变化的话,会立刻回调我们更新UI的代码,就达到了我们的目的。
    • 这里省略了ViewModel的代码,因为这里如果贴出ViewModel的代码,又得贴出很多关于FactoryRepository的代码,具体的源码可以去Github项目地址查看。

你可能感兴趣的:(源码分析,移动端安卓开发,Jetpack,Archtecture)