安卓分页加载器——Paging使用指南

一、简介

应用开发过程中分页加载时很普遍的需求,它能节省数据流量,提升应用的性能。 Google为了方便开发者完成分页加载而推出了分页组件—Paging。为几种常见的分页机制提供了统一的解决方案。

  • 优势
    • 分页数据的内存中缓存。该功能可确保应用在处理分页数据时高效利用系统资源。
    • 内置的请求重复信息删除功能,可确保应用高效利用网络带宽和系统资源。
    • 可配置的RecyclerView适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
    • 对Kotlin协程和Flow以及LiveData和RxJava的一流支持。
    • 内置对错误处理功能的支持,包括刷新和重试功能。
  • 数据来源:Paging支持三种数据架构类型
    • 网络:对网络数据进行分页加载是最常见的需求。API接口通常不太一样,Paging提供了三种不同的方案,应对不同的分页机制。Paging不提供任务错误处理功能,发生错误后可重试网络请求。
    • 数据库:数据库进行分页加载和网络类似,推荐使用Room数据库修改和插入数据。
    • 网络+数据库:通常只采用单一数据源作为解决方案,从网络获取数据,直接缓存进数据库,列表直接从数据库中获取数据。

二、核心

2.1 核心类

Paging的工作原理主要涉及三个类:

  1. PagedListAdapter:RecyclerView.Adapter基类,用于在RecyclerView显示来自PagedList的分页数据。
  2. PagedList:PagedList负责通知DataSource何时获取数据,如加载第一页、最后一页及加载数量等。从DataSource获取的数据将存储在PagedList中。
  3. DataSource:执行具体的数据载入工作,数据载入需要在工作线程中进行

以上三个类的关系及数据加载流程如下图:

20181021221030916.gif

当一条新的item插入到数据库,DataSource会被初始化,LiveData后台线程就会创建一个新的PagedList。这个新的PagedList会被发送到UI线程的PagedListAdapter中,PagedListAdapter使用DiffUtil在对比现在的Item和新建Item的差异。当对比结束,PagedListAdapter通过调用RecycleView.Adapter.notifyItemInserted()将新的item插入到适当的位置

2.2 DataSource

根据分页机制的不同,Paing为我们提供了三种DataSource。

  1. PositionalDataSource

适用于可通过任意位置加载数据,且目标数据源数量固定的情况。

  1. PageKeyedDataSource

适合数据源以“页”的方式进行请求的情况。如获取数据携带pagepageSize时。本文代码使用此DataSource

  1. ItemKeyedDataSource

适用于当目标数据的下一页需要依赖上一页数据中的最后一个对象中的某个字段作为key的情况,如评论数据的接口携带参数sincepageSize

三、使用

3.1 构建自己的DataSource

DataSource控制数据加载,包括初始化加载,加载上页数据,加载下页数据。此处我们以PageKeyedDataSource为例

//泛型参数未Key Value,Key就是每页的标志,此处为Long,Value为数据类型
class ListDataSource : PageKeyedDataSource<Long, Item>() {
//重试加载时的参数
private var lastLoadParam: Pair<LoadParams<Long>, LoadCallback<Long, Item>>? = null

}

其中的关键点在于,每次Key的选定以及loadInitialloadBeforeloadAfter三个函数的重写。PageKeyedDataSource的Key一般依赖与服务端返回的数据。

3.2 构建PagedList

companion object{

private const val TAG = "List"
const val PAGE_SIZE = 5
const val FETCH_DIS = 1

}
val ListData: LiveData<PagedList<Item>> = LivePagedListBuilder(
dataSourceFactory,
Config(
PAGE_SIZE,
FETCH_DIS,
true
)
).build()

其中PAGE_SIZE是每页的数量,FETCH_DIS是距离最后一个数据item还有多少距离就触发加载动作。

此处ListData是LiveData类型,因此可以在Activity中进行监听,当发生数据变化时,则刷新adapter:

ListViewModel.ListData.observe(this) {
adapter.submitList(it)
}

3.3 构建自己的PagedListAdapter

一定要继承PagedListAdapter<Item, RecyclerView.ViewHolder>(``POST_COMPARATOR``)POST_COMPARATOR就是DiffUtil,PagedListAdapter使用DiffUtil在对比现在的Item和新建Item的差异。

typealias ItemClickListener = (Item) -> Unit
typealias onClickListener = () -> Unit

class ListAdapter(
pri
}
}

可以看到基本写法和普通的RecyclerView.Adapter是差不多的,只是多了DiffUtil,使用起来也是一样:

adapter = ListAdapter(
this,
onItemClickListener,
headRetryClickListener,
footRetryClickListener
)
list_rv.adapter = adapter

四、Paging 3.0

Paging3与旧版Paging存在很大区别。Paging2.x运行起来的效果无限滑动还不错,不过代码写起来有点麻烦,功能也不是太完善,比如下拉刷新的方法都没有提供,我们还得自己去调用DataSource#invalidate()方法重置数据来实现。Paging3.0功能更加强大,用起来更简单。

4.1 区别

  • DataSource

Paing2中的DataSource有三种,Paging3中将它们合并到了PagingSource中,实现load()和getRefreshKey(),在Paging3中,所有加载方法参数被一个LoadParams密封类替代,该类中包含了每个加载类型所对应的子类。如果需要区分load()中的加载类型,需要检查传入了LoadParams的哪个子类

  • PagedListAdapter

Adapter不在继承PagedListAdapter,而是由PagingDataAdapter替代,其它不变。

class ArticleAdapter : PagingDataAdapter<Article,ArticleViewHolder>(POST_COMPARATOR){

companion object{

val POST_COMPARATOR = object : DiffUtil.ItemCallback<Article>() {
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean =
oldItem == newItem

override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean =
oldItem.id == newItem.id
}
}

override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
holder.tvName.text = getItem(position)?.title
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.list_item,parent,false))
}
}

class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val tvName: TextView = itemView.findViewById(R.id.tvname)
}

4.2 获取数据并设置给Adapter

google提倡我使用三层架构来完成数据到Adapter的设置,如下图

image.png

代码库层

代码库层中的主要 Paging 库组件是 PagingSource。每个 PagingSource 对象都定义了数据源,以及如何从该数据源检索数据。PagingSource 对象可以从任何单个数据源(包括网络来源和本地数据库)加载数据。可使用的另一个 Paging 库组件是 RemoteMediatorRemoteMediator 对象会处理来自分层数据源(例如具有本地数据库缓存的网络数据源)的分页。

ViewModel 层

Pager 组件提供了一个公共 API,基于 PagingSource 对象和 PagingConfig 配置对象来构造在响应式流中公开的 PagingData 实例。将 ViewModel 层连接到界面的组件是 PagingData。 PagingData 对象是用于存放分页数据快照的容器。它会查询 PagingSource 对象并存储结果。

界面层

界面层中的主要 Paging 库组件是 PagingDataAdapter

0 个评论

要回复文章请先登录注册