Android Jetpack 库架构组件 Room+Paging 基础使用

上篇文章 Android Jetpack 库架构组件 ViewModel+LiveData 基础使用示例2中,使用 ViewModel+ LiveData 的方式实现了数据库数据查询并分页显示的效果,而这里的数据库用的就是Room,分页使用的 Paging

Room 是什么

Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。

也就是 Room 是在SQlite的基础上封装了接口,使得SQlite更加易用。

使用Room需要包含 3 个主要组件:

  • 数据库:包含数据库持有者。可以通过调用 Room.databaseBuilder()Room.inMemoryDatabaseBuilder() 获取 Database 的实例。
  • Entity:表示数据库中的表。
  • DAO:包含用于访问数据库的方法。

Room 不同组件之间的关系如下图所示:

步骤1:应用使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。:
步骤2:应用使用每个 DAO 从数据库中获取实体,然后再将对这些实体的所有更改保存回数据库中。
步骤3:应用使用实体来获取和设置与数据库中的表列相对应的值。

在这里插入图片描述

Room 使用步骤

示例:
(1)获取数据库中学生的姓名列表

  1. Module -> build.gradle的引入

版本依赖查看:https://developer.android.google.cn/jetpack/androidx/releases/room#declaring_dependencies

	def room_version = "2.2.5"

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

	// 还有一些可选项,可查看上面的版本依赖链接
  1. 定义实体类,用@Entity注解
@Entity(tableName = "Student")
data class Student(
	// 字段1,主键自增
	@PrimaryKey(autoGenerate = true) 
	val id: Int, 
	// 字段2
	val name: String)
    
  1. 创建 DAO接口,用@Dao注解实现数据库的增删改查
@Dao
interface StudentDao {

	// DataSource.Factory<Int, Student> 获取 Room 数据库中所有学生名称按升序返回
    @Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
    fun getAllStudent(): DataSource.Factory<Int, Student>

	// 插入学生集合
    @Insert
    fun insert(students: List<Student>)

	// 插入一个学生
    @Insert
    fun insert(student: Student)
}
  1. 创建AppDatabase扩展 RoomDatabase 的抽象类,并创建数据库实例。
// 把实体类添加到数组中,定义数据库版本号
@Database(entities = [Student::class], version = 1)
abstract class StudentDb:RoomDatabase() {
    // 定义 DAO
    abstract fun studentDao(): StudentDao
	
	// 静态方法创建实例和往数据库插入学生姓名信息
    companion object {
        private var instance: StudentDb? = null
        @Synchronized
        fun get(): StudentDb {
            if (instance == null) {
                instance = Room.databaseBuilder(applicationContext,
                    StudentDb::class.java, "StudentDatabase").build()
            }
            return instance!!
        }
        // 默认数据
        fun initData(){
            ioThread {
                // 单线程池
                get().studentDao().insert(
                    CHEESE_DATA.map {
                        Student(
                            id = 0,
                            name = it
                        )
                    })
            }
        }
    }
}

// 学生姓名
private val CHEESE_DATA = arrayListOf(
    "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
    "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag",
    "Airedale", "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert",  // 15
    "American Cheese", "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro",
    "Appenzell", "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String",
    "Aromes au Gene de Marc", "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", // 30
    "Avaxtskyr", "Baby Swiss", "Babybel", "Baguette Laonnaise", "Bakers",
    "Baladi", "Balaton", "Bandal", "Banon", "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
    "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
    "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
    "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
    "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
    "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)"
)
  1. 获取数据,展示
class HomeViewModel(context: Context): BaseViewModel() {

    companion object {
        private const val PAGE_SIZE = 15
        private const val ENABLE_PLACEHOLDERS = false
    }

//    val mContext = context
//    val dao = StudentDb.get(mContext).studentDao()

	// 获取 DAO,通过 DAO 去获取数据库数据
    val dao = StudentDb.get().studentDao()
    // dao.getAllStudent() 这里即返回了所有的学生姓名
	
	// 这里使用 Paging 实现分页,文章下面再说。
    val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)                         //配置分页加载的数量
        .setEnablePlaceholders(ENABLE_PLACEHOLDERS)     //配置是否启动PlaceHolders
        .setInitialLoadSizeHint(PAGE_SIZE)              //初始化加载的数量
        .build()).build()
}

Tip:
别忘了在Application调用数据的初始化,不然查询数据库时查不到学生数据。(或者你编写学生姓名一条条插入数据库)

// 初始化数据库数据
StudentDb.initData()

Paging 是什么

分页库可帮助您一次加载和显示一小块数据。按需载入部分数据会减少网络带宽和系统资源的使用量。

分页库的关键组件是 PagedList 类,用于加载应用数据块或页面。随着所需数据的增多,系统会将其分页到现有的 PagedList 对象中。

如果任何已加载的数据发生更改,会从 LiveData 或基于 RxJava2 的对象向可观察数据存储器发出一个新的 PagedList 实例
随着 PagedList 对象的生成,应用界面会呈现其内容,同时还会考虑界面控件的生命周期。

分页库支持以下数据架构:

  • 仅从后端服务器提供,推荐配合 Retrofit 使用。
  • 仅存储在设备上的数据库中,推荐配合 Room 使用。
  • 使用设备上的数据库作为缓存的其他来源组合,推荐配合 Retrofit + Room 使用。

在这里插入图片描述

下面以查询数据库显示在RecyclerView为例介绍使用步骤。

Paging 使用步骤

示例:
(1)将数据库的数据查询出来显示在 RecyclerView

  1. Module -> build.gradle的引入

版本依赖查看:https://developer.android.google.cn/jetpack/androidx/releases/paging#declaring_dependencies

dependencies {
   def paging_version = "2.1.2"

   implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx

   // alternatively - without Android dependencies for testing
   testImplementation "androidx.paging:paging-common:$paging_version" // For Kotlin use paging-common-ktx

   // optional - RxJava support
   implementation "androidx.paging:paging-rxjava2:$paging_version" // For Kotlin use paging-rxjava2-ktx
 }
    
  1. 构建对象。Room 数据库提供了 DataSource.Factory 对象或者 自定义对象
@Dao
interface StudentDao {
	
    @Query("SELECT * FROM Student ORDER BY name COLLATE NOCASE ASC")
    fun getAllStudent(): DataSource.Factory<Int, Student>
}
  1. 生成PagedList。将 DataSource.Factory 的实例传递到 LivePagedListBuilderRxPagedListBuilder 对象。
class HomeViewModel(context: Context): BaseViewModel() {

    companion object {
        private const val PAGE_SIZE = 15
        private const val ENABLE_PLACEHOLDERS = false
    }

    val dao = StudentDb.get().studentDao()
	
	// 传递实例给 LivePagedListBuilder ,分页配置
    val allStudents = LivePagedListBuilder(dao.getAllStudent(), PagedList.Config.Builder()
        .setPageSize(PAGE_SIZE)                         //配置分页加载的数量
        .setEnablePlaceholders(ENABLE_PLACEHOLDERS)     //配置是否启动PlaceHolders
        .setInitialLoadSizeHint(PAGE_SIZE)              //初始化加载的数量
        .build()).build()
}
  1. 创建 Adapter继承PagedListAdapter
    PagedListAdapter继承自RecyclerView.Adapter
    在这里插入图片描述

PagedListAdapter需要接收一个DiffUtil.ItemCallback参数进行对象的构建。

class StudentAdapter: PagedListAdapter<Student, StudentViewHolder>(diffCallback) {

    override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {
        holder.bindTo(getItem(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder =
        StudentViewHolder(parent)

    companion object {
    
    	// 用于计算列表中两个非空 item 之间的差异的回调。
        private val diffCallback = object : DiffUtil.ItemCallback<Student>() {
        
        	// 检查两个对象是否表示同一 item 数据。
            override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean =
                oldItem.id == newItem.id
			
			// 检查两个项目是否具有相同的数据。
            override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean =
                oldItem == newItem
        }
    }
}
  1. UI页面使用数据
override fun initView() {

    val adapter = StudentAdapter()
    val layoutManager = LinearLayoutManager(activity)
    rv_list.layoutManager = layoutManager
    rv_list.adapter = adapter
    
    // 将数据的变化反映到UI上
    viewModel.allStudents.observe(this, Observer {
        adapter.submitList(it)
    })
}

详细使用代码请参见:YGragon/FrameDemo

总结

Paging是分页库,也就是将大量的数据通过一段一段的返回给页面展示。而大量的数据可以从网络请求返回,也可以是从Room数据库中读取。从Room数据库中读取需要创建数据库实例DAOEntity,而将数据展示在列表需要用到PagedListAdapterPagedListAdapter继承自RecyclerView.Adapter,在该Adapter中需要传入diffCallback,用于判断数据是否是最新的。最后就是在UI中通过LiveData监听数据的变化及时更新到 UI

参考

上车

佛系原创号主
在这里插入图片描述

原文地址:https://www.cnblogs.com/gdragon/p/13210579.html