Android jetpack的Paging和Room使用

介紹

Paging主要是用來結(jié)合RecyclerView進(jìn)行使用鸠信,是一種分頁加載解決方案心傀,這樣Paging每次只會(huì)加載總數(shù)據(jù)的一部分。
Room是Google提供的一個(gè)ORM庫。
本文的代碼來自官方例子:官方示例地址

使用Paging Room

  1. 添加依賴

    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"


  1. 數(shù)據(jù)庫的創(chuàng)建
    示例通過 Room數(shù)據(jù)庫獲取數(shù)據(jù)源魂务,用來在Recyclerview展示我們的數(shù)據(jù),但是正常的開發(fā)主要以網(wǎng)絡(luò)請(qǐng)求方式獲取來獲取數(shù)據(jù)源(網(wǎng)絡(luò)請(qǐng)求和Paging的GitHub代碼)泌射。

簡單介紹一下Room粘姜。Room提供了三個(gè)主要的組件:

@Database:@Database用來注解類,并且注解的類必須是繼承自RoomDatabase的抽象類熔酷。該類主要作用是創(chuàng)建數(shù)據(jù)庫和創(chuàng)建Dao孤紧。并且會(huì)生成XXX(類名)_Impl的實(shí)現(xiàn)類

@Entity:@Entity用來注解實(shí)體類,@Database通過entities屬性引用被@Entity注解的類拒秘,并利用該類的所有字段作為表的列名來創(chuàng)建表号显。使用@Database注解的類中必須定一個(gè)不帶參數(shù)的方法,這個(gè)方法返回使用@Dao注解的類

@Dao:@Dao用來注解一個(gè)接口或者抽象方法躺酒,該類的作用是提供訪問數(shù)據(jù)庫的方法押蚤。并且會(huì)生成XXX(類名)_impl的實(shí)現(xiàn)類,
(1)創(chuàng)建Student實(shí)體類:
主要是定義了自增的主鍵

@Entity
data class Student(@PrimaryKey(autoGenerate = true) val id: Int,val name: String)

(2)創(chuàng)建Dao:
定義了一些數(shù)據(jù)庫的操作方法羹应。其中DataSource表示數(shù)據(jù)源的意識(shí)揽碘,數(shù)據(jù)源的改變會(huì)驅(qū)動(dòng)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(.........);

  1. 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的實(shí)現(xiàn)類。
其中的DiffUtil.ItemCallback<> 實(shí)例雳刺,當(dāng)數(shù)據(jù)源發(fā)生變化時(shí)劫灶,會(huì)回調(diào)DiffUtil.ItemCallback中兩個(gè)抽象方法,確認(rèn)數(shù)據(jù)和之前是否發(fā)生了改變掖桦,如果改變則調(diào)用Adapter更新UI本昏。
areItemTheSame方法:是否為同一個(gè)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是用于對(duì)PagedList進(jìn)行構(gòu)建配置的類。
pageSize用于指定每頁數(shù)據(jù)量料饥。
enablePlaceholders表示是否將未加載的數(shù)據(jù)以null存儲(chǔ)在在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>>進(jìn)行添加一個(gè)觀察者,每當(dāng)觀察到數(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)當(dāng)一條新的數(shù)據(jù)插入到數(shù)據(jù)庫即數(shù)據(jù)源發(fā)生變化時(shí),會(huì)回調(diào)到DataSource.InvalidatedCallback巡蘸,在ComputableLiveData的compute方法中DataSource會(huì)被初始化(dataSourceFactory.create())奋隶。
(2)LiveData后臺(tái)線程就會(huì)創(chuàng)建一個(gè)新的PagedList。新的PagedList會(huì)被mLiveData.postValue(value)發(fā)送到UI線程的PagedListAdapter中悦荒。
(3)PagedListAdapter使用DiffUtil在對(duì)比現(xiàn)在的Item和新建Item的差異唯欣。當(dāng)對(duì)比結(jié)束,PagedListAdapter通過調(diào)用RecycleView.Adapter.notifyItemInserted()將新的item插入到適當(dāng)?shù)奈恢谩?/p>

具體流程如下圖所示:


在這里插入圖片描述

主要的幾個(gè)類:
(1)DataSource: 數(shù)據(jù)源搬味,數(shù)據(jù)的改變會(huì)驅(qū)動(dòng)列表的更新境氢。它有三個(gè)主要的子類。

PositionalDataSource: 主要用于加載數(shù)據(jù)可數(shù)有限的數(shù)據(jù)碰纬。比如加載本地?cái)?shù)據(jù)庫萍聊,對(duì)應(yīng)WrapperPositionalDataSource分裝類。還有個(gè)子類LimitOffsetDataSource悦析,其中數(shù)據(jù)庫返回的就是該子類寿桨。

ItemKeyedDataSource:主要用于加載逐漸增加的數(shù)據(jù)。比如說網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)隨著不斷的請(qǐng)求得到的數(shù)據(jù)越來越多强戴,對(duì)應(yīng)WrapperItemKeyedDataSource封裝類亭螟。

PageKeyedDataSource:這個(gè)和ItemKeyedDataSource有些相似,都是針對(duì)那種不斷增加的數(shù)據(jù)酌泰。這里網(wǎng)絡(luò)請(qǐng)求得到數(shù)據(jù)是分頁的媒佣,對(duì)應(yīng)WrapperPageKeyedDataSource封裝類。

(2)PageList: 核心類陵刹,它從數(shù)據(jù)源取出數(shù)據(jù)默伍,同時(shí)欢嘿,它負(fù)責(zé)控制第一次默認(rèn)加載多少數(shù)據(jù),之后每一次加載多少數(shù)據(jù)等等也糊,并將數(shù)據(jù)的變更反映到UI上炼蹦。
(4)DataSource.Factory這個(gè)接口的實(shí)現(xiàn)類主要是用來獲取的DataSource數(shù)據(jù)源。
(5)PagedListAdapter繼承自RecyclerView.Adapter狸剃,RecyclerView的適配器掐隐,通過DiffUtil分析數(shù)據(jù)是否發(fā)生了改變,負(fù)責(zé)處理UI展示的邏輯钞馁。
(6)LivePagedListBuilder通過這個(gè)類來生成對(duì)應(yīng)的PagedList虑省,內(nèi)部主要有ComputableLiveData類。
主要的流程和類如下所示:

官網(wǎng)說明地址

在這里插入圖片描述
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末僧凰,一起剝皮案震驚了整個(gè)濱河市探颈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌训措,老刑警劉巖伪节,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绩鸣,居然都是意外死亡怀大,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門呀闻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來化借,“玉大人,你說我怎么就攤上這事捡多∑流ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵局服,是天一觀的道長。 經(jīng)常有香客問我驳遵,道長淫奔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任堤结,我火速辦了婚禮唆迁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竞穷。我一直安慰自己唐责,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布瘾带。 她就那樣靜靜地躺著鼠哥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朴恳,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天抄罕,我揣著相機(jī)與錄音,去河邊找鬼于颖。 笑死呆贿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的森渐。 我是一名探鬼主播做入,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼同衣!你這毒婦竟也來了竟块?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤乳怎,失蹤者是張志新(化名)和其女友劉穎彩郊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚪缀,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秫逝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了询枚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片违帆。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖金蜀,靈堂內(nèi)的尸體忽然破棺而出刷后,到底是詐尸還是另有隱情,我是刑警寧澤渊抄,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布尝胆,位于F島的核電站,受9級(jí)特大地震影響护桦,放射性物質(zhì)發(fā)生泄漏含衔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一二庵、第九天 我趴在偏房一處隱蔽的房頂上張望贪染。 院中可真熱鬧,春花似錦催享、人聲如沸杭隙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痰憎。三九已至票髓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間信殊,已是汗流浹背炬称。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涡拘,地道東北人玲躯。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像鳄乏,于是被迫代替她去往敵國和親跷车。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容