带你彻底了解Android Jetpack组件的Paging库
本位由作者 烧麦 投稿 https://github.com/shaomaicheng/Article/blob/master/paging/paging.md
初次接除 paging, 可能会一脸懵逼,感觉出来了很多 API, 不知道从哪里下手。我们先对 paging 的组成部分进行一个了解。
首先,我们按照 列表分页加载
这个行为进行一个基本的划分,分为 2 个部分, 数据
和 UI
, paging 就是按照这个来进行划分的
数据
数据部分 paging 包括
-
PagedList
一个继承了AbstractList
的List
子类, 包括了数据源获取的数据 -
DataSource
数据源的概念,分别提供了 PageKeyedDataSource、ItemKeyedDataSource、PositionalDataSource, 在数据源中,我们可以定义我们自己的数据加载逻辑。
UI
UI 部分 paging 提供了一个新的 PagedListAdapter
, 在实例化这个 Adapter
的时候,我们需要提供一个自己实现的 DiffUtil.ItemCallback
或者 AsyncDifferConfig
入门
以分页数据源 PageKeyedDataSource
为例
创建一个数据源, 其中 Language 为 demo 中的实体对象
class LanguageDataSource: PageKeyedDataSource<Int, Language>()
实现三个 override 方法
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Language>) {
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Language>) {
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Language>) {
}
着 3 个方法,依次解释为
- 初次加载
- 后面一页加载
- 前一页加载
我们给第一页数据填充逻辑
LanguageRepository.requestLanguages({datas->
if (datas.code == 200) {
val languages = datas.data
Handler(Looper.getMainLooper()).post {
callback.onResult(languages, null, 1)
}
} else {
}
}, {t->
Log.e(javaClass.simpleName, "${t.message}")
})
其中 LanguageRepository
是利用 retrofit
请求了一个 Language 对象的列表。 我们调用
callback.onResult
就会刷新 RecyclerView 的视图
loadAfter
的实现大致与 loadInitial
一致,这里不做赘述。
我们再来看一下 UI 层,我们定义一个 PagedListAdapter
class LanguageAdapter(private val context: Context) : PagedListAdapter<Language, ViewHolder>(languageDiff)
这里我们需要 override 2个方法
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
override fun onBindViewHolder(holder: ViewHolder, position: Int)
在 onBindViewHolder
中, 我们可以通过 getItem(position)
获取相对于的数据实例去进行 UI 的展示。
接下来是一个比较关键的部分,那就是如何连接 DATA 和 UI 这两部分。
val config = PagedList.Config.Builder()
.setPageSize(15)
.setPrefetchDistance(2)
.setInitialLoadSizeHint(15)
.setEnablePlaceholders(false)
.build()
val pageList = PagedList.Builder(LanguageDataSource(), config)
.setNotifyExecutor {
Handler(Looper.getMainLooper()).post {it.run()}
}
.setFetchExecutor(Executors.newFixedThreadPool(2))
.build()
adapter.submitList(pageList)
在这里, pageList 的 NotifyExecutor
和 FetchExecutor
也是必须设置的。在 Android arch componet
完整的架构中,更推荐使用构建一个 PageList
的 LiveData
的方式。但是不使用也没有关系,arch compoent
的完整内容在这里不做过多的描述。具体的详细使用可以查看google的实例源码
在大致了解了 paging 的组成部分后,我们会开始好奇,那我们到底为什么需要 paging 呢, 他和我们之前普通的使用方式有什么区别呢,我们可以在源码中寻找到答案。
我们可以在 2 个部分的真正对接处作为切入点进行分析,查看 PagedList.Builder#build()
的源码:
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
继续查看
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
跟到这个类的构造方法,可以看到如下逻辑
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
这里以 PageKeyedDataSource
为例, 其他的 DataSource
对象同理
查看 dispatchLoadInital
方法
LoadInitialCallbackImpl<Key, Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
这里我们可以看到, loadInitial
就是我们需要在 override 的方法之一。那我们里面调用 callback 的 onResult 方法到底发生了什么呢?
查看 LoadInitialCallbackImpl#onResult()
的源码,关键逻辑如下
mDataSource.initKeys(previousPageKey, nextPageKey);
int trailingUnloadedCount = totalCount - position - data.size();
if (mCountingEnabled) {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
data, position, trailingUnloadedCount, 0));
} else {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
}
查看 dispatchResultToReceiver
继续查看 onPageResult
方法
我们关注一下 init
时候的逻辑
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
init
的逻辑很简单,只有 2 行
init(leadingNulls, page, trailingNulls, positionOffset);
callback.onInitialized(size());
在这里, 我们可以看见关键的逻辑
mPages.clear();
mPages.add(page);
这里,和 PageList
绑定的数据就发生了变化。之后我们把 PageList
submit 给了 adapter
那么,数据就发生了更新。
初始加载我们看完了,那么,剩下的数据是如何加载的呢
我们反过来看 RecyclerView
, 如果我们滑动列表或者其他操作的时候,很自然会调用 adapter 的 bind 方法。那么,我们去查看 PagedListAdapter#getItem
的源码。
return mDiffer.getItem(position);
查看 PageList
的 loadAround
loadAroundInternal(index);
继续,
if (mAppendItemsRequested > 0) {
scheduleAppend();
}
查看 scheduleAppend
的实现
mBackgroundThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (isDetached()) {
return;
}
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,
mMainThreadExecutor, mReceiver);
}
}
});
这里,我们看到了 dispatchLoadAfter
方法的调用,之后的逻辑和之前的 dispathLoadInitial
就非常的类似了。
最终,会调用到如下逻辑
这里会走 AsyncPagedListDiffer
的 PagedList.Callback
的回调
这里,callback 是和 adapter 关联起来的。所以会在这里刷新列表。
最后,我们看一下 Adapter
的 submit 方法,最后可以看到这样的逻辑
我们可以看到 paging 是利用了 DiffUtils
对 RecyclerView 进行刷新的。这样我们也无需担心 paging 会存在性能问题。
理解
最后谈一下对 paging 的理解。 一般情况下,我们最原始的方式,列表 UI 所在的部分,是需要知道数据的来源等逻辑部分,我们在常见的 mvp 模式中,会对数据和 UI 进行分层。 而 paging 就利用一系列的封装, 提供了更加通用的 API 调用来做这些事情。更通俗点说,就是实现了分页加载结构中的 Presenter 层及 Presenter层的下游处理部分。
这种模式,业务的编写者,可以把 UI 部分的代码模板化, 只需要关心业务逻辑,并且把业务逻辑中的数据获取写在 DataSource 中,使分页加载的操作解耦程度更高。
— — — END — — —
- HLS Lesson20-实例分析
- WannaCry勒索病毒详细解读
- matlab GUI基础3
- 不使用反射的实体类方案
- matlab GUI基础1
- Why to do,What to do,Where to do 与 Lambda表达式!
- Cloak ; Dagger攻击:一种可针对所有版本Android的攻击技术(含演示视频)
- 实例探究字符编码:unicode,utf-8,default,gb2312 的区别
- 分布式计算,WCF+JSON+实体对象与WebService+DataSet效率大比拼
- 【自然框架】 页面里的父类—— 改进和想法、解释
- 线性神经网络
- 【数据可视化】深度解析大数据可视化设计案例分析
- 使用IE6看老赵的博客——比较完美版(可以在线查看、回复)
- 【Python环境】R vs Python:硬碰硬的数据分析
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Android 获取进程名函数,如何优化到极致?
- R语言做K均值聚类的一个简单小例子
- 如何自动化Salesforce应用程序
- 2万字带你学习Qos原理,还有6个实验案例,建议一定要收藏!
- 最全总结 | 聊聊 Python 数据处理全家桶(MongoDB 篇)
- R函数
- unstack函数应用——生成单细胞marker基因表格
- 手把手教你使用Python打造一个智能搜索淘宝商品,生成操作日志的系统
- leetcode树之将有序数组转换为二叉搜索树
- Mysql必知必会!
- 【一天一大 lee】路径总和 II (难度:中等) - Day20200926
- 【一天一大 lee】从中序与后序遍历序列构造二叉树 (难度:中等)-Day20200925
- 【一天一大 lee】填充每个节点的下一个右侧节点指针 II (难度:中等) - Day20200928
- 【一天一大 lee】 二叉搜索树中的众数 (难度:简单)-Day20200924
- 【一天一大 lee】 合并二叉树 (难度:简单)-Day20200923