介紹
Paging主要是用來結(jié)合RecyclerView進行使用,是一種分頁加載解決方案,這樣Paging每次只會加載總數(shù)據(jù)的一部分。
Room是Google提供的一個ORM庫。
本文的代碼來自官方例子:官方示例地址
使用Paging Room
- 添加依賴
def room_version = "2.2.0-alpha02"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-rxjava2:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
kapt "androidx.room:room-compiler:$room_version"
def paging = "2.1.0"
implementation "androidx.paging:paging-runtime-ktx:$paging"
- 數(shù)據(jù)庫的創(chuàng)建
示例通過 Room數(shù)據(jù)庫獲取數(shù)據(jù)源,用來在Recyclerview展示我們的數(shù)據(jù),但是正常的開發(fā)主要以網(wǎng)絡請求方式獲取來獲取數(shù)據(jù)源(網(wǎng)絡請求和Paging的GitHub代碼)。
簡單介紹一下Room。Room提供了三個主要的組件:
@Database:@Database用來注解類,并且注解的類必須是繼承自RoomDatabase的抽象類。該類主要作用是創(chuàng)建數(shù)據(jù)庫和創(chuàng)建Dao。并且會生成XXX(類名)_Impl的實現(xiàn)類
@Entity:@Entity用來注解實體類,@Database通過entities屬性引用被@Entity注解的類,并利用該類的所有字段作為表的列名來創(chuàng)建表。使用@Database注解的類中必須定一個不帶參數(shù)的方法,這個方法返回使用@Dao注解的類
@Dao:@Dao用來注解一個接口或者抽象方法,該類的作用是提供訪問數(shù)據(jù)庫的方法。并且會生成XXX(類名)_impl的實現(xiàn)類,
(1)創(chuàng)建Student實體類:
主要是定義了自增的主鍵
@Entity
data class Student(@PrimaryKey(autoGenerate = true) val id: Int,val name: String)
(2)創(chuàng)建Dao:
定義了一些數(shù)據(jù)庫的操作方法。其中DataSource表示數(shù)據(jù)源的意識,數(shù)據(jù)源的改變會驅(qū)動UI的更新。
@Dao
interface StudentDao {
@Query("Select * from Student ORDER BY name COLLATE NOCASE ASC ")
fun queryByName(): DataSource.Factory<Int,Student>
@Insert`在這里插入代碼片`
fun insert(students: List<Student>)
@Insert
fun insert(student: Student)
@Delete
fun delete(student: Student)
}
(3)創(chuàng)建數(shù)據(jù)庫:
@Database(entities = arrayOf(Student::class) ,version = 1)
abstract class StudentDb : RoomDatabase(){
abstract fun studentDao(): StudentDao
companion object{
private var instance: StudentDb? = null
@Synchronized
fun get(context: Context): StudentDb{
if(instance == null){
instance = Room.databaseBuilder(context.applicationContext,
StudentDb::class.java,"StudentDataBase")
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
fillInDb(context.applicationContext)
}
}).build()
}
return instance!!
}
private fun fillInDb(context: Context){
ioThread{
get(context).studentDao().insert(STUDENT_DATA.map {
Student(id = 0,name = it)
})
}
}
}
}
private val STUDENT_DATA = arrayListOf(.........);
- UI顯示
(1)創(chuàng)建StudentViewHolder
class StudentViewHolder (parent: ViewGroup) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.adapter_paging,parent,false)){
private val nameView = itemView.findViewById<TextView>(R.id.name)
var student : Student? = null
fun bindTo(student: Student?){
this.student = student
nameView.text = student?.name
}
}
(2)創(chuàng)建PagedListAdapter的實現(xiàn)類。
其中的DiffUtil.ItemCallback<> 實例,當數(shù)據(jù)源發(fā)生變化時,會回調(diào)DiffUtil.ItemCallback中兩個抽象方法,確認數(shù)據(jù)和之前是否發(fā)生了改變,如果改變則調(diào)用Adapter更新UI。
areItemTheSame方法:是否為同一個Item
areContentsTheSame方法:數(shù)據(jù)內(nèi)容是否發(fā)生變化
class StudentAdapter : PagedListAdapter<Student,StudentViewHolder>(diffCallback){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder {
return StudentViewHolder(parent)
}
override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
companion object{
private val diffCallback = object : DiffUtil.ItemCallback<Student>(){
override fun areContentsTheSame(oldItem: Student, newItem: Student): Boolean {
Log.e("tag","areContentsTheSame"+Thread.currentThread().name+ oldItem.name +" new :"+newItem.name)
return oldItem.id == newItem.id
}
override fun areItemsTheSame(oldItem: Student, newItem: Student): Boolean {
Log.e("tag","areItemsTheSame"+ oldItem.name +" new :"+newItem.name)
return oldItem == newItem
}
}
}
}
(3)創(chuàng)建ViewModel
toLiveData方法內(nèi)部通過LivePagedListBuilder來構(gòu)建PagedList。返回的是LiveData<PagedList<Value>>,用于UI層監(jiān)聽數(shù)據(jù)源變化。
Config是用于對PagedList進行構(gòu)建配置的類。
pageSize用于指定每頁數(shù)據(jù)量。
enablePlaceholders表示是否將未加載的數(shù)據(jù)以null存儲在在PageList中。
class StudentViewModel (app: Application) : AndroidViewModel(app){
val dao = StudentDb.get(app).studentDao()
val allStudents = dao.queryByName().toLiveData(Config(pageSize = 30,enablePlaceholders = true,maxSize = Int.MAX_VALUE))
fun insert(name: String) = ioThread{
dao.insert(Student(id = 0,name = name))
}
fun remove(student: Student) = ioThread{
dao.delete(student)
}
}
(4)Activity中使用
在Activity中給ViewModel中LiveData<PagedList<Person>>進行添加一個觀察者,每當觀察到數(shù)據(jù)源中數(shù)據(jù)的變化,就可以調(diào)用StudentAdapter的submitList方法把最新的數(shù)據(jù)交給Adapter去展示了。
class PagingActivity : AppCompatActivity(){
private lateinit var viewModel: StudentViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paging)
viewModel = ViewModelProviders.of(this).get(StudentViewModel::class.java)
val adapter = StudentAdapter()
studenrecy.adapter = adapter
viewModel.allStudents.observe(this, Observer(adapter::submitList))
initAddButtonListener()
initSwipeToDelete()
}
private fun initSwipeToDelete(){
ItemTouchHelper(object : ItemTouchHelper.Callback(){
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(0,ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
(viewHolder as StudentViewHolder).student?.let { viewModel.remove(it) }
}
}).attachToRecyclerView(studenrecy)
}
private fun addCheese() {
val newCheese = inputText.text.trim()
if (newCheese.isNotEmpty()) {
viewModel.insert(newCheese.toString())
inputText.setText("")
}
}
private fun initAddButtonListener(){
addButton.setOnClickListener {
addCheese()
}
// when the user taps the "Done" button in the on screen keyboard, save the item.
inputText.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
addCheese()
return@setOnEditorActionListener true
}
false // action that isn't DONE occurred - ignore
}
// When the user clicks on the button, or presses enter, save the item.
inputText.setOnKeyListener { _, keyCode, event ->
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
addCheese()
return@setOnKeyListener true
}
false // event that isn't DOWN or ENTER occurred - ignore
}
}
}
小結(jié)
總結(jié)一下大概的流程:
(1)當一條新的數(shù)據(jù)插入到數(shù)據(jù)庫即數(shù)據(jù)源發(fā)生變化時,會回調(diào)到DataSource.InvalidatedCallback,在ComputableLiveData的compute方法中DataSource會被初始化(dataSourceFactory.create())。
(2)LiveData后臺線程就會創(chuàng)建一個新的PagedList。新的PagedList會被mLiveData.postValue(value)發(fā)送到UI線程的PagedListAdapter中。
(3)PagedListAdapter使用DiffUtil在對比現(xiàn)在的Item和新建Item的差異。當對比結(jié)束,PagedListAdapter通過調(diào)用RecycleView.Adapter.notifyItemInserted()將新的item插入到適當?shù)奈恢谩?/p>
具體流程如下圖所示:

主要的幾個類:
(1)DataSource: 數(shù)據(jù)源,數(shù)據(jù)的改變會驅(qū)動列表的更新。它有三個主要的子類。
PositionalDataSource: 主要用于加載數(shù)據(jù)可數(shù)有限的數(shù)據(jù)。比如加載本地數(shù)據(jù)庫,對應WrapperPositionalDataSource分裝類。還有個子類LimitOffsetDataSource,其中數(shù)據(jù)庫返回的就是該子類。
ItemKeyedDataSource:主要用于加載逐漸增加的數(shù)據(jù)。比如說網(wǎng)絡請求的數(shù)據(jù)隨著不斷的請求得到的數(shù)據(jù)越來越多,對應WrapperItemKeyedDataSource封裝類。
PageKeyedDataSource:這個和ItemKeyedDataSource有些相似,都是針對那種不斷增加的數(shù)據(jù)。這里網(wǎng)絡請求得到數(shù)據(jù)是分頁的,對應WrapperPageKeyedDataSource封裝類。
(2)PageList: 核心類,它從數(shù)據(jù)源取出數(shù)據(jù),同時,它負責控制第一次默認加載多少數(shù)據(jù),之后每一次加載多少數(shù)據(jù)等等,并將數(shù)據(jù)的變更反映到UI上。
(4)DataSource.Factory這個接口的實現(xiàn)類主要是用來獲取的DataSource數(shù)據(jù)源。
(5)PagedListAdapter繼承自RecyclerView.Adapter,RecyclerView的適配器,通過DiffUtil分析數(shù)據(jù)是否發(fā)生了改變,負責處理UI展示的邏輯。
(6)LivePagedListBuilder通過這個類來生成對應的PagedList,內(nèi)部主要有ComputableLiveData類。
主要的流程和類如下所示: