当前位置:   article > 正文

Paging Library使用及原理

paging library

Paging Library使用及原理

简介

paging是google推出的分页加载框架,收录在 jetpack开发套件,结合RecycleView使用,开发者只用选择合适的模板实现自己的DataSource(数据存储层,可以是内存/db/网络),框架层实现了自动分页加载的逻辑,详情可以参考官方文档: developer.android.com/topic/libra…

Demo示例

先来一个简单的示例,分页加载学生列表,模拟了100个学生数据,id从0开始自增,以id为cursor分页加载,每页10条数据 效果如下:

  • 添加gradle依赖

  1. dependencies {
  2. ...
  3. implementation ("android.arch.paging:runtime:1.0.1")
  4. implementation 'android.arch.lifecycle:extensions:1.1.1'
  5. }
  6. 复制代码
  • 示例代码

  • 选择合适的DataSource

    • 一共3种DataSource可选,取决于你的数据是以何种方式分页加载:
      • ItemKeyedDataSource:基于cursor实现,数据容量可动态自增
      • PageKeyedDataSource:基于页码实现,数据容量可动态自增
      • PositionalDataSource:数据容量固定,基于index加载特定范围的数据
    • 学生数据以id自增排序,以id作为分页加载的cursor,所以这里我们选择ItemKeyedDataSource
    1. public class StudentDataSource extends ItemKeyedDataSource<String, StudentBean> {
    2. private static final int MIN_STUDENT_ID = 1;
    3. private static final int MAX_STUDENT_ID = 100;
    4. private Random mRandom = new Random();
    5. public StudentDataSource() {
    6. }
    7. @Override
    8. public void loadInitial(@NonNull LoadInitialParams<String> params,
    9. @NonNull LoadInitialCallback<StudentBean> callback) {
    10. List<StudentBean> studentBeanList = mockStudentBean(0L, params.requestedLoadSize);
    11. callback.onResult(studentBeanList);
    12. }
    13. @Override
    14. public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<StudentBean> callback) {
    15. long studentId = Long.valueOf(params.key);
    16. int limit = (int)Math.min(params.requestedLoadSize, Math.max(MAX_STUDENT_ID - studentId, 0));
    17. List<StudentBean> studentBeanList = mockStudentBean(studentId + 1, limit);
    18.  callback.onResult(studentBeanList);
    19. }
    20. @Override
    21. public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<StudentBean> callback) {
    22. long studentId = Long.valueOf(params.key);
    23. int limit = (int)Math.min(params.requestedLoadSize, Math.max(studentId - MIN_STUDENT_ID, 0));
    24.    List<StudentBean> studentBeanList = mockStudentBean(studentId - limit, limit);
    25. callback.onResult(studentBeanList);
    26. }
    27. @NonNull
    28. @Override
    29. public String getKey(@NonNull StudentBean item) {
    30. return item.getId();
    31. }
    32. }
    33. 复制代码
  • 实现DataSource工厂(可选,Demo使用了LivePagedListBuilder,依赖Factory)

    • 这里实现的工厂逻辑很简单,只是实例化一个DataSource
    1. public class StudentDataSourceFactory extends DataSource.Factory<String, StudentBean> {
    2. @Override
    3. public DataSource<String, StudentBean> create() {
    4. return new StudentDataSource();
    5. }
    6. }
    7. 复制代码
  • 生成PageList

    • 生成PageList有连个必要参数
      • DataSource:前面已经介绍过
      • PagedList.Config,包含以下配置:
        • pageSize:每页加载数量
        • prefetchDistance:提前多少个item开始加载下(上)一页数据,默认为pageSize
        • initialLoadSizeHint:初始化多少条数据,默认是pageSize*3
        • enablePlaceholders:是否支持占位符显示(只有列表size固定的情况下有效)
    • 依赖LivePagedListBuilder生成LiveData(持有一个PageList实例)
    1. public class StudentRepositoryImpl implements IStudentRepository {
    2. @Override
    3. public LiveData<PagedList<StudentBean>> getAllStudents() {
    4. int pageSize = 10;
    5. StudentDataSourceFactory dataSourceFactory = new StudentDataSourceFactory();
    6. PagedList.Config pageListConfig = new PagedList.Config.Builder()
    7. .setEnablePlaceholders(false)
    8. .setInitialLoadSizeHint(pageSize * 2)
    9. .setPageSize(pageSize)
    10. .build();
    11. return new LivePagedListBuilder<>(dataSourceFactory, pageListConfig)
    12. .build();
    13. }
    14. }
    15. 复制代码
    • builde内部构建PageList代码如下:
    1. mList = new PagedList.Builder<>(mDataSource, config)
    2. .setNotifyExecutor(notifyExecutor)
    3. .setFetchExecutor(fetchExecutor)
    4. .setBoundaryCallback(boundaryCallback)
    5. .setInitialKey(initializeKey)
    6. .build();
    7. 复制代码
  • 实现PagedListAdapter

    • 和ListAdaper一样,需要需要自定义Diff规则
  1. public class StudentAdapter extends PagedListAdapter<StudentBean, StudentViewHolder> {
  2. private static final DiffUtil.ItemCallback<StudentBean> DIFF_CALLBACK = new ItemCallback<StudentBean>() {
  3. @Override
  4. public boolean areItemsTheSame(StudentBean oldItem, StudentBean newItem) {
  5. return TextUtils.equals(oldItem.getId(), newItem.getId());
  6. }
  7. @Override
  8. public boolean areContentsTheSame(StudentBean oldItem, StudentBean newItem) {
  9. return oldItem == newItem;
  10. }
  11. };
  12. public StudentAdapter() {
  13. super(DIFF_CALLBACK);
  14. }
  15. @Override
  16. public StudentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  17. View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.student_item, null, false);
  18. return new StudentViewHolder(itemView);
  19. }
  20. @Override
  21. public void onBindViewHolder(StudentViewHolder holder, int position) {
  22. holder.bindData(getItem(position));
  23. }
  24. 复制代码
  • 绑定PageList到PagedListAdapter
  1. StudentViewModel viewModel = ViewModelProviders.of(this).get(StudentViewModel.class);
  2. viewModel.getPageListLiveData().observe(this, new Observer<PagedList<StudentBean>>() {
  3. @Override
  4. public void onChanged(@Nullable PagedList<StudentBean> studentBeans) {
  5. studentAdapter.submitList(studentBeans);
  6. }
  7. });
  8. 复制代码
  • 分页加载日志

源码分析

  • 数据加载原理图

  • 大致流程如下:

    • 条件触发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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/286494
推荐阅读
相关标签
  

闽ICP备14008679号