来新公司半年多,最近一直在参与 Andorid 团队的架构升级工作。最近在图片选择库中使用了 paging 作为分页加载框架。顺便阅读了一下paging的源码。在这里记录一下。

初次接除 paging, 可能会一脸懵逼,感觉出来了很多 API, 不知道从哪里下手。我们先对 paging 的组成部分进行一个了解。

首先,我们按照 列表分页加载 这个行为进行一个基本的划分,分为 2 个部分, 数据UI, paging 就是按照这个来进行划分的

数据

数据部分 paging 包括

  • PagedList 一个继承了 AbstractListList 子类, 包括了数据源获取的数据
  • 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.dataHandler(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 的 NotifyExecutorFetchExecutor 也是必须设置的。在 Android arch componet 完整的架构中,更推荐使用构建一个 PageListLiveData 的方式。但是不使用也没有关系,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);
复制代码

查看 PageListloadAround

loadAroundInternal(index);
复制代码

继续,

if (mAppendItemsRequested > 0) {scheduleAppend();
}
复制代码

查看 scheduleAppend 的实现

mBackgroundThreadExecutor.execute(new Runnable() {@Overridepublic void run() {if (isDetached()) {return;}if (mDataSource.isInvalid()) {detach();} else {mDataSource.dispatchLoadAfter(position, item, mConfig.pageSize,mMainThreadExecutor, mReceiver);}}});
复制代码

这里,我们看到了 dispatchLoadAfter 方法的调用,之后的逻辑和之前的 dispathLoadInitial 就非常的类似了。

最终,会调用到如下逻辑

这里会走 AsyncPagedListDifferPagedList.Callback 的回调

这里,callback 是和 adapter 关联起来的。所以会在这里刷新列表。

最后,我们看一下 Adapter 的 submit 方法,最后可以看到这样的逻辑

我们可以看到 paging 是利用了 DiffUtils 对 RecyclerView 进行刷新的。这样我们也无需担心 paging 会存在性能问题。

总结

最后谈一下对 paging 的理解。 一般情况下,我们最原始的方式,列表 UI 所在的部分,是需要知道数据的来源等逻辑部分,我们在常见的 mvp 模式中,会对数据和 UI 进行分层。 而 paging 就利用一系列的封装, 提供了更加通用的 API 调用来做这些事情。更通俗点说,就是实现了分页加载结构中的 Presenter 层及 Presenter层的下游处理部分。

这种模式,业务的编写者,可以把 UI 部分的代码模板化, 只需要关心业务逻辑,并且把业务逻辑中的数据获取写在 DataSource 中,使分页加载的操作解耦程度更高。

深入理解Android Paging分页加载库相关推荐

  1. Android常用图片加载库介绍及对比

    前言 图片加载在 Android开发项目中是必不可少的,为了降低开发周期和难度,我们经常会选用一些图片加载的开源库,而Android发展到现在图片加载开源库也越来越多了,下面介绍几种开发中主流的图片加 ...

  2. android Glide图片加载库使用

    Glide是一款由Bump Technologies开发的图片加载框架,使得我们可以在Android平台上以极度简单的方式加载和展示图片. 要想使用Glide,首先需要将这个库引入到我们的项目当中.新 ...

  3. Android新浪微博分页加载,Android仿新浪微博自定义ListView下拉刷新(4)

    自定义PullToRefreshListView继承ListView,在ListView头部添加一个下拉的头部布局.跟ListView用法完全一致. 此处详细介绍Adapter的详细代码. 1.首先给 ...

  4. Android:图片加载库Glide VS Picasso

    两个库拥有许多特性,以下列入部分特性比较 Picasso (CircleTransform.Java) import android.graphics.Bitmap; import com.squar ...

  5. Carson带你学Android:主流开源图片加载库对比(UIL、Picasso、Glide、Fresco)

    前言 图片加载在 Android开发项目中十分常见 为了降低开发周期 & 难度,我们经常会选用一些图片加载的开源库,而现在图片加载开源库越来越多,我们应该选用哪种呢? 今天.我就给大家介绍 & ...

  6. Android平滑图片加载和缓存库Glide使用详解

    在图片加载库烂大街的今天,选择一个适合自己使用的图片加载库已经成为了每一个Android开发者的必经之路.现在市面上知名的图片加载库有UIL,Picasso,Volley ImageLoader,Fr ...

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

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

  8. android图片加载库Glide

    什么是Glide? Glide是一个加载图片的库,作者是bumptech,它是在泰国举行的google 开发者论坛上google为我们介绍的,这个库被广泛的运用在google的开源项目中. Glide ...

  9. Android图片加载库的封装实战

    重磅更新 2017-02-16 2017-05-09 优化圆形图片加载 更新demo 前言 主流图片加载库的对比 Android-Universal-Image-Loader Picasso Glid ...

最新文章

  1. 专注文本处理,达观数据完成B轮融资,累计融资超2亿元
  2. 基于VTKITK的Qt应用程序开发
  3. 使用Windbg调试StackOverflowException异常
  4. Python八种数据导入方法,你掌握了吗?
  5. 尽量使用写文本方式存储数据(pandas 和 file write效率对比)
  6. jsp内置对象(四)-----session对象
  7. Ural 1001 Reverse Root 解题报告
  8. 一次docker中的nginx进程响应慢问题定位记录
  9. makefile 使用 Tricks
  10. 网络安全系统性学习路线「全文字详细介绍」
  11. 世界淡水资源占水资源的多少_全球的淡水资源占水资源比例为多少
  12. acwing算法基础课
  13. linux 安装 blas,Linux下Linpack安装过程
  14. winedit使用教程_latex及winedit入门指导教程.pdf
  15. F1DC2706蓝牙模块的射频特性及功耗测试
  16. 完蛋,公司被一条 update 语句干趴了!
  17. 令程序员泪流满面的瞬间
  18. 争对让望对思野葛对山栀注解_解析
  19. 浅谈Python中的type()、dtype()、astype()的区别
  20. DLL 导出方法(两种)

热门文章

  1. 关于c语言中负数位移位操作的漫谈
  2. ie下LI的间距问题
  3. java为窗体添加滚动条
  4. SQL Server 2005:你应该知道的13件事情
  5. Python科学计算工具:NumPy第一部分
  6. 5、【华为HCIE-Storage】--RAID类型
  7. ES6常用知识点概述
  8. SQLServer错误:过程 sp_addextendedproperty,第 xxx 行对象无效。'dbo.xxx.xxx' 不允许有扩展属性,或对象不存在。...
  9. Android 事件与事件监听器
  10. putty和Xming server 结合完美在windows下显示linux GUI程序