之前的几篇源码分析我们分别对
Navigation
、Lifecycles
、ViewModel
、LiveData
、进行了分析,也对JetPack有了更深入的了解。但是Jetpack远不止这些组件,今天的主角—Paging,Jetpack中的分页组件,官方是这么形容它的:‘’逐步从您的数据源按需加载信息‘’
如果你对Jetpack组件有了解或者想对源码有更深入的了解,请看我之前的几篇文章:
1. Jetpack源码解析—看完你就知道Navigation是什么了?
2. Jetpack源码解析—Navigation为什么切换Fragment会重绘?
3. Jetpack源码解析—用Lifecycles管理生命周期
4. Jetpack源码解析—LiveData的使用及工作原理
5. Jetpack源码解析—ViewModel基本使用及源码解析
1. 背景
在我的Jetpack_Note系列中,对每一篇的分析都有相对应的代码片段及使用,我把它做成了一个APP,目前功能还不完善,代码我也上传到了GitHub上,参考了官方的Demo以及目前网上的一些文章,有兴趣的小伙伴可以看一下,别忘了给个Star。
https://github.com/Hankkin/JetPack_Note
今天我们的主角是Paging,介绍之前我们先看一下效果:
2. 简介
2.1 基本介绍
官方定义:
分页库Pagin Library是Jetpack的一部分,它可以妥善的逐步加载数据,帮助您一次加载和显示一部分数据,这样的按需加载可以减少网络贷款和系统资源的使用。分页库支持加载有限以及无限的list,比如一个持续更新的信息源,分页库可以与RecycleView无缝集合,它还可以与LiveData或RxJava集成,观察界面中的数据变化。
2.2 核心组件
1. PagedList
PageList是一个集合类,它以分块的形式异步加载数据,每一块我们称之为页。它继承自AbstractList
,支持所有List的操作,它的内部有五个主要变量:
- mMainThreadExecutor 主线程Executor,用于将结果传递到主线程
- mBackgroundThreadExecutor 后台线程,执行负载业务逻辑
- BoundaryCallback 当界面显示缓存中靠近结尾的数据的时候,它将加载更多的数据
- Config PageList从DataSource中加载数据的配置
- PagedStorage 用于存储加载到的数据
Config属性:
- pageSize:分页加载的数量
- prefetchDistance:预加载的数量
- initialLoadSizeHint:初始化数据时加载的数量,默认为pageSize*3
- enablePlaceholders:当item为null是否使用placeholder显示
PageList会通过DataSource加载数据,通过Config的配置,可以设置一次加载的数量以及预加载的数量。除此之外,PageList还可以想RecycleView.Adapter发送更新的信号,驱动UI的刷新。
2. DataSource
DataSource<Key,Value> 顾名思义就是数据源,它是一个抽象类,其中Key
对应加载数据的条件信息,Value
对应加载数据的实体类。Paging库中提供了三个子类来让我们在不同场景的情况下使用:
- PageKeyedDataSource:如果后端API返回数据是分页之后的,可以使用它;例如:官方Demo中GitHub API中的SearchRespositories就可以返回分页数据,我们在GitHub API的请求中制定查询关键字和想要的哪一页,同时也可以指明每个页面的项数。
- ItemKeyedDataSource:如果通过键值请求后端数据;例如我们需要获取在某个特定日期起Github的前100项代码提交记录,该日期将成为DataSource的键,ItemKeyedDataSource允许自定义如何加载初始页;该场景多用于评论信息等类似请求
- PositionalDataSource:适用于目标数据总数固定,通过特定的位置加载数据,这里Key是Integer类型的位置信息,T即Value。 比如从数据库中的1200条开始加在20条数据。
3. PagedListAdapter
PageListAdapter继承自RecycleView.Adapter,和RecycleView实现方式一样,当数据加载完毕时,通知RecycleView数据加载完毕,RecycleView填充数据;当数据发生变化时,PageListAdapter会接受到通知,交给委托类AsyncPagedListDiffer来处理,AsyncPagedListDiffer是对**DiffUtil.ItemCallback**持有对象的委托类,AsyncPagedListDiffer使用后台线程来计算PagedList的改变,item是否改变,由DiffUtil.ItemCallback决定。
3.基本使用
3.1 添加依赖包
implementation "androidx.paging:paging-runtime:$paging_version" // For Kotlin use paging-runtime-ktx
implementation "androidx.paging:paging-runtime-ktx:$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
3.2 PagingWithRoom使用
新建UserDao
/**
* created by Hankkin
* on 2019-07-19
*/
@Dao
interface UserDao {
@Query("SELECT * FROM User ORDER BY name COLLATE NOCASE ASC")
fun queryUsersByName(): DataSource.Factory<Int, User>
@Insert
fun insert(users: List<User>)
@Insert
fun insert(user: User)
@Delete
fun delete(user: User)
}
创建UserDB数据库
/**
* created by Hankkin
* on 2019-07-19
*/
@Database(entities = arrayOf(User::class), version = 1)
abstract class UserDB : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private var instance: UserDB? = null
@Synchronized
fun get(context: Context): UserDB {
if (instance == null) {
instance = Room.databaseBuilder(context.applicationContext,
UserDB::class.java, "UserDatabase")
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
fillInDb(context.applicationContext)
}
}).build()
}
return instance!!
}
/**
* fill database with list of cheeses
*/
private fun fillInDb(context: Context) {
// inserts in Room are executed on the current thread, so we insert in the background
ioThread {
get(context).userDao().insert(
CHEESE_DATA.map {
User(id = 0, name = it) })
}
}
}
}
创建PageListAdapter
/**
* created by Hankkin
* on 2019-07-19
*/
class PagingDemoAdapter : PagedListAdapter<User, P