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();}}
    复制代码
  • 实现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();
    复制代码
  • 实现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与PageList组合使用,不同的DataSource子类适用于不同的PageList子类

    • ContiguousDataSource:可以动态扩容,基于"页码"或"游标"进行分页加载

      • ItemKeyedDataSource:基于cursor分页加载,抽象类,子类需要实现loadXXX加载数据
      • PageKeyedDataSource:基于页码分页加载,抽象类,子类需要实现LoadXXX加载数据
    • PositionalDataSource:基于postion分页加载特定范围的数据
      • LimitOffsetDataSource:基于DB实现的固定size的数据源,依赖Room,抽象类,子类需要实现convertRows将cursor转换成数据Bean
      • TiledDataSource:Room1.0版本依赖这个类型,后续可能会替换成PositionalDataSource,抽象类,Room框架apt自动生成代码
  • 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使用及原理相关推荐

  1. paging library java_Android官方分页组件介绍之Paging的使用详解

    Android官方分页组件介绍之Paging的使用详解 发布时间:2018-04-27 13:47, 浏览次数:1618 , 标签: Android Paging Paging 使您的应用程序更容易从 ...

  2. Android Jetpack组件之 Paging使用-源码

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  3. android 使用4大组件的源码,Android Jetpack架构组件之 Paging(使用、源码篇)

    1.前言 最近简单看了下google推出的框架Jetpack,感觉此框架的内容可以对平时的开发有很大的帮助,也可以解决很多开发中的问题,对代码的逻辑和UI界面实现深层解耦,打造数据驱动型UI界面. A ...

  4. Android官方架构组件Paging:分页库的设计美学

    本文已授权 微信公众号 玉刚说 (@任玉刚)独家发布. 2019/12/24 补充 距本文发布时隔一年,笔者认为,本文不应该作为入门教程的第一篇博客,相反,读者真正想要理解 Paging 的使用,应该 ...

  5. Android Architecture Components 整理

    Android Architecture Components是谷歌在Google I/O 2017发布一套帮助开发者解决Android架构设计的方案. 里面包含了两大块内容: 生命周期相关的Life ...

  6. 基于Android官方AsyncListUtil优化经典ListView分页加载机制(二)

    基于Android官方AsyncListUtil优化经典ListView分页加载机制(二) 我写的附录文章1,介绍了如何使用Android官方的分页加载框架AsyncListUtil优化改进常见的Re ...

  7. 【Altium Designer】DatabaseLib的使用方法

    前言 使用Altium Designer数据库DatabaseLib功能可以方便地把元器件与公司内的原理图库.PCB库以及器件的参数进行链接,减少人为操作的失误,并可提高输出BOM的工作效率. 准备材 ...

  8. 使用基于 WebRTC 的 JavaScript API 在浏览器环境里调用本机摄像头

    HTML5,JavaScript 和现代浏览器这套三驾马车的组合,使得传统的 Web 应用较之过去能实现更多更丰富的同用户交互的功能.摄像头如今已成为智能手机的标配,前端 Web 应用也出现了越来越多 ...

  9. Altium Designer之Preferences

    Schematic General 1.Break Wires At Autojumctions:交叉点切断平行线,切断之后两边可以单独选中进行编辑. 2.Optimize wires & B ...

最新文章

  1. r语言中调用c 程序,如何在R程序包中调用C函数
  2. appium-java自动化框架设计学习
  3. 【 Vivado 】通过IP Integrator进行设计示例
  4. 单例模式 之 单例模式——饿汉模式
  5. linux 内存 shared,Linux Shared Memory的查看与设置
  6. 伪共享(False Sharing)
  7. java是很厉害的么_java总是很强大吗?
  8. C#中怎样在ToolStripMenuItem下再添加子级菜单
  9. NYOJ 692 Chinese checkers(广搜)
  10. array_reduce() 与 array_map()
  11. html table导出到Excel中,不走后台,js完成
  12. python 逐行读取csv_在R中如何逐行读取CSV文件并将内容识别为正确的数据类型?...
  13. el表达式与jstl的用法
  14. oracle实现id自增和设置主键
  15. java 建表 框架_【Java框架型项目从入门到装逼】第九节 - 数据库建表和CRUD操作...
  16. Python合并Excel2007+中多个WorkSheet
  17. 工程勘察设计收费标准2002修订版_2020抚顺花海工程设计收费标准
  18. 《数据库原理与应用》复习总结
  19. python与jay的龙卷风
  20. 一步一步 copy163: 网易严选 ---- vue-cli

热门文章

  1. 2017年诺贝尔生理学或医学奖揭晓
  2. 让计算机拥有一双眼睛,人工智能科学家已经努力了半个世纪
  3. 程序员如何乘风破浪?从数据库历史看技术人发展 | CSDN 高校俱乐部
  4. CSDN七夕包分配,最后一天啦!
  5. HashFlare矿池退出BTC挖矿,Coingeek矿池继续增加BCH算力
  6. Android NDK 使用自己的共享库(Import Module)
  7. 特定构造方法 如何让子类重写某些方法时提醒调用super
  8. 【整理】SYSCOMMAND的wParam值的宏定义
  9. Java字符串就该这样设计
  10. XenServer 显示当前使用者的列表