Paging Library使用及原理
Paging Library使用及原理
简介
paging是google推出的分页加载框架,收录在 jetpack开发套件,结合RecycleView使用,开发者只用选择合适的模板实现自己的DataSource(数据存储层,可以是内存/db/网络),框架层实现了自动分页加载的逻辑,详情可以参考官方文档: developer.android.com/topic/libra…
Demo示例
先来一个简单的示例,分页加载学生列表,模拟了100个学生数据,id从0开始自增,以id为cursor分页加载,每页10条数据 效果如下:
添加gradle依赖
dependencies {...implementation ("android.arch.paging:runtime:1.0.1")implementation 'android.arch.lifecycle:extensions:1.1.1'
}
复制代码
示例代码
选择合适的DataSource
- 一共3种DataSource可选,取决于你的数据是以何种方式分页加载:
- ItemKeyedDataSource:基于cursor实现,数据容量可动态自增
- PageKeyedDataSource:基于页码实现,数据容量可动态自增
- PositionalDataSource:数据容量固定,基于index加载特定范围的数据
- 学生数据以id自增排序,以id作为分页加载的cursor,所以这里我们选择ItemKeyedDataSource
public class StudentDataSource extends ItemKeyedDataSource<String, StudentBean> {private static final int MIN_STUDENT_ID = 1;private static final int MAX_STUDENT_ID = 100;private Random mRandom = new Random();public StudentDataSource() {}@Overridepublic void loadInitial(@NonNull LoadInitialParams<String> params,@NonNull LoadInitialCallback<StudentBean> callback) {List<StudentBean> studentBeanList = mockStudentBean(0L, params.requestedLoadSize);callback.onResult(studentBeanList);}@Overridepublic void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<StudentBean> callback) {long studentId = Long.valueOf(params.key);int limit = (int)Math.min(params.requestedLoadSize, Math.max(MAX_STUDENT_ID - studentId, 0));List<StudentBean> studentBeanList = mockStudentBean(studentId + 1, limit);callback.onResult(studentBeanList);}@Overridepublic void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<StudentBean> callback) {long studentId = Long.valueOf(params.key);int limit = (int)Math.min(params.requestedLoadSize, Math.max(studentId - MIN_STUDENT_ID, 0));List<StudentBean> studentBeanList = mockStudentBean(studentId - limit, limit);callback.onResult(studentBeanList);}@NonNull@Overridepublic String getKey(@NonNull StudentBean item) {return item.getId();}} 复制代码
- 一共3种DataSource可选,取决于你的数据是以何种方式分页加载:
实现DataSource工厂(可选,Demo使用了LivePagedListBuilder,依赖Factory)
- 这里实现的工厂逻辑很简单,只是实例化一个DataSource
public class StudentDataSourceFactory extends DataSource.Factory<String, StudentBean> {@Overridepublic DataSource<String, StudentBean> create() {return new StudentDataSource();} }复制代码
生成PageList
- 生成PageList有连个必要参数
- DataSource:前面已经介绍过
- PagedList.Config,包含以下配置:
- pageSize:每页加载数量
- prefetchDistance:提前多少个item开始加载下(上)一页数据,默认为pageSize
- initialLoadSizeHint:初始化多少条数据,默认是pageSize*3
- enablePlaceholders:是否支持占位符显示(只有列表size固定的情况下有效)
- 依赖LivePagedListBuilder生成LiveData(持有一个PageList实例)
public class StudentRepositoryImpl implements IStudentRepository {@Overridepublic LiveData<PagedList<StudentBean>> getAllStudents() {int pageSize = 10;StudentDataSourceFactory dataSourceFactory = new StudentDataSourceFactory();PagedList.Config pageListConfig = new PagedList.Config.Builder().setEnablePlaceholders(false).setInitialLoadSizeHint(pageSize * 2).setPageSize(pageSize).build();return new LivePagedListBuilder<>(dataSourceFactory, pageListConfig).build();} } 复制代码
- builde内部构建PageList代码如下:
mList = new PagedList.Builder<>(mDataSource, config).setNotifyExecutor(notifyExecutor).setFetchExecutor(fetchExecutor).setBoundaryCallback(boundaryCallback).setInitialKey(initializeKey).build(); 复制代码
- 生成PageList有连个必要参数
实现PagedListAdapter
- 和ListAdaper一样,需要需要自定义Diff规则
public class StudentAdapter extends PagedListAdapter<StudentBean, StudentViewHolder> {private static final DiffUtil.ItemCallback<StudentBean> DIFF_CALLBACK = new ItemCallback<StudentBean>() {@Overridepublic boolean areItemsTheSame(StudentBean oldItem, StudentBean newItem) {return TextUtils.equals(oldItem.getId(), newItem.getId());}@Overridepublic boolean areContentsTheSame(StudentBean oldItem, StudentBean newItem) {return oldItem == newItem;}};public StudentAdapter() {super(DIFF_CALLBACK);}@Overridepublic StudentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.student_item, null, false);return new StudentViewHolder(itemView);}@Overridepublic void onBindViewHolder(StudentViewHolder holder, int position) {holder.bindData(getItem(position));}
复制代码
- 绑定PageList到PagedListAdapter
StudentViewModel viewModel = ViewModelProviders.of(this).get(StudentViewModel.class);viewModel.getPageListLiveData().observe(this, new Observer<PagedList<StudentBean>>() {@Overridepublic void onChanged(@Nullable PagedList<StudentBean> studentBeans) {studentAdapter.submitList(studentBeans);}});
复制代码
分页加载日志
源码分析
数据加载原理图
大致流程如下:
- 条件触发DataSource加载数据,包含两种场景:
- PagedList被创建的时候,会调用DataSource加载初始数据(在当前线程执行)
- 用户滚动列表(距离当前页底部一定距离),自动触发加载下一页数据(默认使用arch框架定义的IO线程池)
- 数据加载完毕,回调到PagedList存储
- PagedList数据发生变化,通知到PagedListAdapter
- PagedListAdapter内部使用DiffUtil计算数据变化(发生在异步线程,不会阻塞UI)
- DiffUtil计算完毕,notify到RecycleView进行局部刷新
- 条件触发DataSource加载数据,包含两种场景:
核心类图
DataSource
- DataSource与PageList组合使用,不同的DataSource子类适用于不同的PageList子类
- ContiguousDataSource:可以动态扩容,基于"页码"或"游标"进行分页加载
- ItemKeyedDataSource:基于cursor分页加载,抽象类,子类需要实现loadXXX加载数据
- PageKeyedDataSource:基于页码分页加载,抽象类,子类需要实现LoadXXX加载数据
- PositionalDataSource:基于postion分页加载特定范围的数据
- LimitOffsetDataSource:基于DB实现的固定size的数据源,依赖Room,抽象类,子类需要实现convertRows将cursor转换成数据Bean
- TiledDataSource:Room1.0版本依赖这个类型,后续可能会替换成PositionalDataSource,抽象类,Room框架apt自动生成代码
- ContiguousDataSource:可以动态扩容,基于"页码"或"游标"进行分页加载
- DataSource支持map变换,类似RxJava的map,可以对value进行类型转换,生成一个新的DataSource(其实是WrapperXXXDataSource包装类,内部依赖Function<List, List>对数据进行转换),map是抽象接口,需要由子类实现具体的变换规则
Factory工厂接口,需要结合LivePagedListBuilder使用
PagedList
两种类型的PagedList
- ContiguousPagedList:持有ContiguousDataSource实例,顾名思义,可以动态扩容,基于"页码"或"游标"进行分页加载
- TiledPagedList:持有PositionalDataSource实例,固定size,基于postion分页加载特定范围的数据
PagedList内部持有以下几个重要成员变量
- Executor:线程调度器,用于执行数据加载和回调接口
- Boundarycallback:触发边界的回调
- PagedListConfig:配置参数
- PagedStorage:真正存储数据的地方
- 内部以页为单位存储数据
- 数据变更后的通知回调,用来通知UI更新
AsyncPagedListDiffer与PagedListAdapter
- AsyncPagedListDiffer:对新旧PagedList进行差分对比
- PagedListAdapter:持有AsyncPagedListDiffer实例,接收PagedList传递给AsyncPagedListDiffer进行差分对比并刷新
数据加载流程图
- 以Demo使用的ItemKeyedDataSource为例,加载下一页的代码调用流程如下:
- 你RecycleView滚动过程中会触发PagedListAdapter#getItem,间接调用AsyncPagedListDiffer#getItem
- AsyncPagedListDiffer内部持有一个PagedList实例,调用ContiguousPagedList#loadAround(该方法在父类PagedList实现),尝试加载下一页数据
- ContiguousPagedList继而调用loadAroundInternal,判断当前是否触达边界(边界取决于prefetchDistance,例如prefetchDistance=5,当前已加载20条数据,那么,当getItem的index>=15就会触发下一页数据加载),如果触发,则异步执行抽象方法dispatchLoadAfter加载下一页数据
- ItemKeyedDataSource实现了dispatchLoadAfter,内部同步调用抽象方法loadAfter(具体的业务代码,Demo中对应StudentDataSource)真正加载数据
- 数据加载完毕,执行dispatchResultToReceiver将结果回传
- 首先会调用ContiguousPagedList内部持有的Receiver实例的onPageResult
- 然后,调用PagedStorage#appendPage,将新的一页数据追加在末尾
- PagedStorage处理完数据,回调onPageAppended(ContiguousPagedList实现了该接口)
- ContiguousPagedList调用notifyChanged/notifyInserted通知所有的观察者
- 观察者AsyncPagedListDiffer收到onInserted/onChanged通知,再通知给PagedListAdapter刷新RecycleView
- 开发者不用监听RecyeleView的滚动来加载下一页,所有的过程全部自动完成,开发者只需要关注自定义的DataSource,按照分页规则,实现数据加载接口即可
后续
理想中的分页加载库只需要用户关注业务数据结构,写少量的代码及UI布局,即可实现分页加载的效果,后续打算基于Paging Libaray封装一套基于"通用分页协议"的"模板代码"
- 该分页开发框架包含以下内容:
- 一套通用的分页协议,与服务端协定
- 依赖网络库及JSON解析库实现默认的网络请求及数据解析
- 依赖ORM的DB方案,实现分页数据的持久化
- 依赖Paging Library实现分页数据缓存/加载/通知更新等一系列动作
- 一定的扩展能力,譬如:数据的装饰,去重,重排等
- 一定的配置能力,譬如:是否持久化以及持久化的页数
- 开发者只需要遵循以下几个步骤即可:
- 遵循通用的分页协议,与服务端协定item数据结构
- 定义item数据Bean
- 自定义扩展能力(可选)
- 参数配置(可选)
- 通过数据Bean的class类型,生成PagedListAdapter
- 自定义视图布局,包括RecycleView以及ItemView,绑定Adapter
转载于:https://juejin.im/post/5cd677d1518825691f48155b
Paging Library使用及原理相关推荐
- paging library java_Android官方分页组件介绍之Paging的使用详解
Android官方分页组件介绍之Paging的使用详解 发布时间:2018-04-27 13:47, 浏览次数:1618 , 标签: Android Paging Paging 使您的应用程序更容易从 ...
- Android Jetpack组件之 Paging使用-源码
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)
1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...
- Android官方架构组件Paging:分页库的设计美学
本文已授权 微信公众号 玉刚说 (@任玉刚)独家发布. 2019/12/24 补充 距本文发布时隔一年,笔者认为,本文不应该作为入门教程的第一篇博客,相反,读者真正想要理解 Paging 的使用,应该 ...
- Android Architecture Components 整理
Android Architecture Components是谷歌在Google I/O 2017发布一套帮助开发者解决Android架构设计的方案. 里面包含了两大块内容: 生命周期相关的Life ...
- 基于Android官方AsyncListUtil优化经典ListView分页加载机制(二)
基于Android官方AsyncListUtil优化经典ListView分页加载机制(二) 我写的附录文章1,介绍了如何使用Android官方的分页加载框架AsyncListUtil优化改进常见的Re ...
- 【Altium Designer】DatabaseLib的使用方法
前言 使用Altium Designer数据库DatabaseLib功能可以方便地把元器件与公司内的原理图库.PCB库以及器件的参数进行链接,减少人为操作的失误,并可提高输出BOM的工作效率. 准备材 ...
- 使用基于 WebRTC 的 JavaScript API 在浏览器环境里调用本机摄像头
HTML5,JavaScript 和现代浏览器这套三驾马车的组合,使得传统的 Web 应用较之过去能实现更多更丰富的同用户交互的功能.摄像头如今已成为智能手机的标配,前端 Web 应用也出现了越来越多 ...
- Altium Designer之Preferences
Schematic General 1.Break Wires At Autojumctions:交叉点切断平行线,切断之后两边可以单独选中进行编辑. 2.Optimize wires & B ...
最新文章
- r语言中调用c 程序,如何在R程序包中调用C函数
- appium-java自动化框架设计学习
- 【 Vivado 】通过IP Integrator进行设计示例
- 单例模式 之 单例模式——饿汉模式
- linux 内存 shared,Linux Shared Memory的查看与设置
- 伪共享(False Sharing)
- java是很厉害的么_java总是很强大吗?
- C#中怎样在ToolStripMenuItem下再添加子级菜单
- NYOJ 692 Chinese checkers(广搜)
- array_reduce() 与 array_map()
- html table导出到Excel中,不走后台,js完成
- python 逐行读取csv_在R中如何逐行读取CSV文件并将内容识别为正确的数据类型?...
- el表达式与jstl的用法
- oracle实现id自增和设置主键
- java 建表 框架_【Java框架型项目从入门到装逼】第九节 - 数据库建表和CRUD操作...
- Python合并Excel2007+中多个WorkSheet
- 工程勘察设计收费标准2002修订版_2020抚顺花海工程设计收费标准
- 《数据库原理与应用》复习总结
- python与jay的龙卷风
- 一步一步 copy163: 网易严选 ---- vue-cli