在本教程中邀泉,我將向您展示如何在Android應(yīng)用程序中使用Android架構(gòu)組件中的Paging庫(kù)和Room支持的數(shù)據(jù)庫(kù)。
您將學(xué)習(xí)如何使用Paging庫(kù)從Room支持的數(shù)據(jù)庫(kù)高效加載大型數(shù)據(jù)集 - 在RecyclerView中滾動(dòng)時(shí)為您的用戶提供更流暢的體驗(yàn)迫靖。
什么是分頁(yè)庫(kù)?
Paging庫(kù)是添加到Architecture Components的另一個(gè)庫(kù)悔常。該庫(kù)有助于有效地管理大型數(shù)據(jù)集的加載和顯示RecyclerView
循帐。根據(jù)官方文件:
分頁(yè)庫(kù)使您可以更輕松地在應(yīng)用程序中逐漸和優(yōu)雅地加載數(shù)據(jù)
RecyclerView
。
如果Android應(yīng)用程序的任何部分要從本地或遠(yuǎn)程數(shù)據(jù)源顯示大型數(shù)據(jù)集,但一次只顯示部分?jǐn)?shù)據(jù)集砾跃,則應(yīng)考慮使用分頁(yè)庫(kù)骏啰。這有助于提高應(yīng)用的性能!
那么為什么要使用尋呼庫(kù)抽高?
既然您已經(jīng)看過(guò)Paging庫(kù)的介紹判耕,您可能會(huì)問(wèn),為什么要使用它翘骂?以下是您應(yīng)該考慮在加載大型數(shù)據(jù)集時(shí)使用它的一些原因RecyclerView
壁熄。
- 它不會(huì)請(qǐng)求不需要的數(shù)據(jù)。當(dāng)用戶滾動(dòng)列表時(shí)碳竟,此庫(kù)僅請(qǐng)求用戶可見(jiàn)的數(shù)據(jù)草丧。
- 節(jié)省用戶的電池并消耗更少的帶寬。因?yàn)樗徽?qǐng)求所需的數(shù)據(jù)莹桅,所以這節(jié)省了一些設(shè)備資源昌执。
在處理大量數(shù)據(jù)時(shí)效率不高,因?yàn)榈讓訑?shù)據(jù)源會(huì)檢索所有數(shù)據(jù)诈泼,即使只有一部分?jǐn)?shù)據(jù)要顯示給用戶懂拾。在這種情況下,我們應(yīng)該考慮分頁(yè)數(shù)據(jù)铐达。
1. 創(chuàng)建Android Studio項(xiàng)目
啟動(dòng)Android Studio 3并創(chuàng)建一個(gè)名為空活動(dòng)的新項(xiàng)目 MainActivity
岖赋。一定要檢查包含Kotlin支持。
2. 添加架構(gòu)組件
創(chuàng)建新項(xiàng)目后瓮孙,在build.gradle中添加以下依賴項(xiàng)唐断。在本教程中,我們使用的是最新的Paging庫(kù)版本1.0.1杭抠,而Room是1.1.1(截至撰寫(xiě)本文時(shí))栗涂。
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "android.arch.persistence.room:runtime:1.1.1"
kapt "android.arch.persistence.room:compiler:1.1.1"
implementation "android.arch.paging:runtime:1.0.1"
implementation "com.android.support:recyclerview-v7:27.1.1"
}
這些工件可在Google的Maven存儲(chǔ)庫(kù)中找到。
allprojects {
repositories {
google()
jcenter()
}
}
通過(guò)添加依賴項(xiàng)祈争,我們已經(jīng)教過(guò)Gradle如何查找?guī)旖锍獭4_保在添加項(xiàng)目后記得同步項(xiàng)目。
3. 創(chuàng)建實(shí)體
創(chuàng)建一個(gè)新的Kotlin數(shù)據(jù)類Person菩混。為簡(jiǎn)單起見(jiàn)忿墅,我們的Person實(shí)體只有兩個(gè)字段:
一個(gè)唯一的ID(id)
人的名字(name)
另外,包括一個(gè)toString( 簡(jiǎn)單返回的方法name沮峡。
import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
@Entity(tableName = "persons")
data class Person(
@PrimaryKey val id: String,
val name: String
) {
override fun toString() = name
}
4. 創(chuàng)建DAO
如您所知疚脐,我們需要使用Room庫(kù)來(lái)訪問(wèn)應(yīng)用程序的數(shù)據(jù),我們需要數(shù)據(jù)訪問(wèn)對(duì)象(DAO)邢疙。在我們自己的案例中棍弄,我們創(chuàng)建了一個(gè) PersonDao望薄。
import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.arch.persistence.room.Dao
import android.arch.persistence.room.Delete
import android.arch.persistence.room.Insert
import android.arch.persistence.room.Query
@Dao
interface PersonDao {
@Query("SELECT * FROM persons")
fun getAll(): LiveData<List<Person>>
@Query("SELECT * FROM persons")
fun getAllPaged(): DataSource.Factory<Int, Person>
@Insert
fun insertAll(persons: List<Person>)
@Delete
fun delete(person: Person)
}
在我們PersonDao班上,我們有兩種@Query方法呼畸。其中之一是 getAll()痕支,它返回一個(gè)LiveData包含Person對(duì)象列表的東西。另一個(gè)是 getAllPaged()蛮原,返回一個(gè)DataSource.Factory卧须。
根據(jù)官方文件,該DataSource課程是:
用于將快照數(shù)據(jù)頁(yè)面加載到的基類PagedList儒陨。
A PagedList是一種List在Android中顯示分頁(yè)數(shù)據(jù)的特殊類型:
A PagedList是一個(gè)List花嘶,它從a中以塊(頁(yè))加載其數(shù)據(jù)DataSource”哪可以使用椭员,訪問(wèn)項(xiàng)目get(int),并可以觸發(fā)進(jìn)一步加載loadAround(int)笛园。
我們Factory在DataSource類中調(diào)用靜態(tài)方法隘击,該方法用作工廠(創(chuàng)建對(duì)象而不必指定將要?jiǎng)?chuàng)建的對(duì)象的確切類)DataSource。此靜態(tài)方法有兩種數(shù)據(jù)類型:
標(biāo)識(shí)項(xiàng)目的關(guān)鍵DataSource喘沿。請(qǐng)注意闸度,對(duì)于Room查詢竭贩,頁(yè)面是編號(hào)的 - 因此我們將其Integer用作頁(yè)面標(biāo)識(shí)符類型蚜印。使用Paging庫(kù)可以使用“鍵控”頁(yè)面,但Room目前不提供留量。
DataSources 加載的列表中的項(xiàng)目或?qū)嶓w(POJO)的類型窄赋。
5. 創(chuàng)建數(shù)據(jù)庫(kù)
這是我們的Room數(shù)據(jù)庫(kù)類的AppDatabase樣子:
import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.content.Context
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.chikeandroid.pagingtutsplus.utils.DATABASE_NAME
import com.chikeandroid.pagingtutsplus.workers.SeedDatabaseWorker
@Database(entities = [Person::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun personDao(): PersonDao
companion object {
// For Singleton instantiation
@Volatile private var instance: AppDatabase? = null
fun getInstance(context: Context): AppDatabase {
return instance ?: synchronized(this) {
instance
?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME)
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val request = OneTimeWorkRequestBuilder<SeedDatabaseWorker>().build()
WorkManager.getInstance()?.enqueue(request)
}
})
.build()
}
}
}
在這里,我們創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù)實(shí)例楼熄,并使用新的WorkManager API預(yù)先填充了數(shù)據(jù)忆绰。請(qǐng)注意,預(yù)先填充的數(shù)據(jù)只是1,000個(gè)名稱的列表(深入了解提供的示例源代碼以了解更多信息)可岂。
6. 創(chuàng)建ViewModel
為了讓我們以生命周期意識(shí)的方式存儲(chǔ)错敢,觀察和提供數(shù)據(jù),我們需要一個(gè)ViewModel
缕粹。我們的PersonsViewModel
稚茅,它擴(kuò)展了AndroidViewModel
類,是要作為我們的ViewModel
平斩。
import android.app.Application
import android.arch.lifecycle.AndroidViewModel
import android.arch.lifecycle.LiveData
import android.arch.paging.DataSource
import android.arch.paging.LivePagedListBuilder
import android.arch.paging.PagedList
import com.chikeandroid.pagingtutsplus.data.AppDatabase
import com.chikeandroid.pagingtutsplus.data.Person
class PersonsViewModel constructor(application: Application)
: AndroidViewModel(application) {
private var personsLiveData: LiveData<PagedList<Person>>
init {
val factory: DataSource.Factory<Int, Person> =
AppDatabase.getInstance(getApplication()).personDao().getAllPaged()
val pagedListBuilder: LivePagedListBuilder<Int, Person> = LivePagedListBuilder<Int, Person>(factory,
50)
personsLiveData = pagedListBuilder.build()
}
fun getPersonsLiveData() = personsLiveData
}
在這個(gè)類中亚享,我們有一個(gè)名為的字段personsLiveData。這個(gè)字段是一個(gè)簡(jiǎn)單的LiveData持有一個(gè)PagedList的 Person對(duì)象绘面。因?yàn)檫@是一個(gè)LiveData欺税,我們的UI(Activity或Fragment)將通過(guò)調(diào)用getter方法來(lái)觀察這些數(shù)據(jù)getPersonsLiveData()侈沪。
我們personsLiveData在init塊內(nèi)初始化。在這個(gè)塊中晚凿,我們DataSource.Factory通過(guò)調(diào)用 對(duì)象的 AppDatabase單例來(lái)獲得 PersonDao亭罪。當(dāng)我們得到這個(gè)對(duì)象時(shí),我們打電話getAllPaged()晃虫。
然后我們創(chuàng)建一個(gè)LivePagedListBuilder皆撩。以下是官方文檔中關(guān)于a的說(shuō)明LivePagedListBuilder:
生成器LiveData<PagedList>,給定a DataSource.Factory和a PagedList.Config哲银。
我們提供構(gòu)造函數(shù)a DataSource.Factory作為第一個(gè)參數(shù)扛吞,頁(yè)面大小作為第二個(gè)參數(shù)(在我們自己的情況下,頁(yè)面大小將為50)荆责。通常滥比,您應(yīng)該選擇一個(gè)大于您可能一次向用戶顯示的最大數(shù)量的大小。最后做院,我們打電話build()給我們構(gòu)建并返回給我們LiveData<PagedList>盲泛。
7. 創(chuàng)建PagedListAdapter
要PagedList在a中顯示我們的數(shù)據(jù)RecyclerView,我們需要一個(gè)PagedListAdapter键耕。以下是官方文檔中對(duì)此類的明確定義:
RecyclerView.Adapter用于從PagedLista中的s 呈現(xiàn)分頁(yè)數(shù)據(jù)的基類RecyclerView寺滚。
所以我們創(chuàng)建了一個(gè)PersonAdapter擴(kuò)展 PagedListAdapter。
import android.arch.paging.PagedListAdapter
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.chikeandroid.pagingtutsplus.R
import com.chikeandroid.pagingtutsplus.data.Person
import kotlinx.android.synthetic.main.item_person.view.*
class PersonAdapter(val context: Context) : PagedListAdapter<Person, PersonAdapter.PersonViewHolder>(PersonDiffCallback()) {
override fun onBindViewHolder(holderPerson: PersonViewHolder, position: Int) {
var person = getItem(position)
if (person == null) {
holderPerson.clear()
} else {
holderPerson.bind(person)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
return PersonViewHolder(LayoutInflater.from(context).inflate(R.layout.item_person,
parent, false))
}
class PersonViewHolder (view: View) : RecyclerView.ViewHolder(view) {
var tvName: TextView = view.name
fun bind(person: Person) {
tvName.text = person.name
}
fun clear() {
tvName.text = null
}
}
}
PagedListAdapter就像使用的任何其他子類一樣使用RecyclerView.Adapter屈雄。換句話說(shuō)村视,你必須實(shí)現(xiàn)方法 onCreateViewHolder()和onBindViewHolder()。
要擴(kuò)展PagedListAdapter抽象類酒奶,您必須提供其構(gòu)造函數(shù) - PageLists(這應(yīng)該是一個(gè)普通的舊Java類:POJO)的類型蚁孔,以及擴(kuò)展ViewHolder適配器將使用的類的類。在我們的例子中惋嚎,我們給它Person杠氢,并PersonViewHolder分別作為第一和第二個(gè)參數(shù)。
請(qǐng)注意另伍,PagedListAdapter需要將其傳遞 DiffUtil.ItemCallback給PageListAdapter構(gòu)造函數(shù)鼻百。DiffUtil是一個(gè)RecyclerView實(shí)用程序類,可以計(jì)算兩個(gè)列表之間的差異摆尝,并輸出將第一個(gè)列表轉(zhuǎn)換為第二個(gè)列表的更新操作列表温艇。ItemCallback是一個(gè)內(nèi)部抽象靜態(tài)類(內(nèi)部DiffUtil),用于計(jì)算列表中兩個(gè)非空項(xiàng)之間的差異结榄。
具體來(lái)說(shuō)中贝,我們提供PersonDiffCallback給我們的 PagedListAdapter構(gòu)造函數(shù)。
import android.support.v7.util.DiffUtil
import com.chikeandroid.pagingtutsplus.data.Person
class PersonDiffCallback : DiffUtil.ItemCallback<Person>() {
override fun areItemsTheSame(oldItem: Person, newItem: Person): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Person?, newItem: Person?): Boolean {
return oldItem == newItem
}
}
因?yàn)槲覀冋趯?shí)現(xiàn)DiffUtil.ItemCallback臼朗,所以我們必須實(shí)現(xiàn)兩種方法:areItemsTheSame()和areContentsTheSame()邻寿。
areItemsTheSame 調(diào)用以檢查兩個(gè)對(duì)象是否代表同一個(gè)項(xiàng)目蝎土。例如,如果您的項(xiàng)目具有唯一ID绣否,則此方法應(yīng)檢查其ID相等誊涯。true如果兩個(gè)項(xiàng)表示相同的對(duì)象或false它們不同,則此方法返回蒜撮。
areContentsTheSame 調(diào)用以檢查兩個(gè)項(xiàng)目是否具有相同的數(shù)據(jù)暴构。true如果項(xiàng)目的內(nèi)容相同或者false它們不同,則此方法返回段磨。
我們的PersonViewHolder內(nèi)心階層只是一個(gè)典型的 RecyclerView.ViewHolder取逾。它負(fù)責(zé)根據(jù)需要將數(shù)據(jù)從我們的模型綁定到列表中的行的小部件中。
class PersonAdapter(val context: Context) : PagedListAdapter<Person, PersonAdapter.PersonViewHolder>(PersonDiffCallback()) {
// ...
class PersonViewHolder (view: View) : RecyclerView.ViewHolder(view) {
var tvName: TextView = view.name
fun bind(person: Person) {
tvName.text = person.name
}
fun clear() {
tvName.text = null
}
}
}
8. 顯示結(jié)果
在我們onCreate()的MainActivity苹支,我們只是做了以下:
viewModel 使用實(shí)用程序類初始化我們的 字段ViewModelProviders
創(chuàng)建一個(gè)實(shí)例 PersonAdapter
配置我們的 RecyclerView
綁定 PersonAdapter到RecyclerView
觀察LiveData并 通過(guò)調(diào)用將PagedList對(duì)象提交給PersonAdaptersubmitList()
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.RecyclerView
import com.chikeandroid.pagingtutsplus.adapter.PersonAdapter
import com.chikeandroid.pagingtutsplus.viewmodels.PersonsViewModel
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: PersonsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(PersonsViewModel::class.java)
val adapter = PersonAdapter(this)
findViewById<RecyclerView>(R.id.name_list).adapter = adapter
subscribeUi(adapter)
}
private fun subscribeUi(adapter: PersonAdapter) {
viewModel.getPersonLiveData().observe(this, Observer { names ->
if (names != null) adapter.submitList(names)
})
}
}
最后砾隅,當(dāng)您運(yùn)行應(yīng)用程序時(shí),結(jié)果如下:
滾動(dòng)時(shí)债蜜,Room可以通過(guò)一次加載50個(gè)項(xiàng)目并將它們提供給我們PersonAdapter
的子類來(lái)防止間隙PagingListAdapter
晴埂。但請(qǐng)注意,并非所有數(shù)據(jù)源都會(huì)快速加載寻定。加載速度還取決于Android設(shè)備的處理能力儒洛。
9.與RxJava集成
如果您正在使用或想要在項(xiàng)目中使用RxJava,則分頁(yè)庫(kù)包含另一個(gè)有用的工件: RxPagedListBuilder
狼速。您使用此工件而不是 LivePagedListBuilder
RxJava支持琅锻。
您只需創(chuàng)建一個(gè)實(shí)例 RxPagedListBuilder
,提供與LivePagedListBuilder
-the DataSource.Factory
和頁(yè)面大小相同的參數(shù) 唐含。然后調(diào)用 buildObservable()
或buildFlowable()
返回一個(gè)Observable
或 Flowable
您的PagedList
分別浅浮。
要顯式提供 Scheduler
數(shù)據(jù)加載工作沫浆,請(qǐng)調(diào)用setter方法 setFetchScheduler()
捷枯。為了提供Scheduler
結(jié)果(例如 AndroidSchedulers.mainThread()
),只需撥打電話即可 setNotifyScheduler()
专执。默認(rèn)情況下淮捆,setNotifyScheduler()
默認(rèn)為UI線程,setFetchScheduler()
默認(rèn)為I / O線程池本股。
結(jié)論
在本教程中攀痊,您學(xué)習(xí)了如何使用Room 的Android架構(gòu)組件(Android Jetpack的一部分)輕松使用Paging組件 。這有助于我們有效地從本地?cái)?shù)據(jù)庫(kù)加載大型數(shù)據(jù)集拄显,以便在滾動(dòng)瀏覽列表時(shí)實(shí)現(xiàn)更流暢的用戶體驗(yàn)RecyclerView
苟径。