一,什么是Paging
Paging 可以使开发者更轻松在 RecyclerView 中 分页加载数据。
官方文档:https://developer.android.com/topic/libraries/architecture/paging/
Sample: https://github.com/googlesamples/android-sunflower
二,主要涉及问题
PageList
这个类是用来存储加载的数据。PagedList中所需要的数据都是从下面要讲的DataSource中进行加载的。
DataSource
这类提供加载所需的数据。也就是在这个类中进行数据的获取操作。数据源可以是DataBase也可以是服务器。
三,如何使用Paging
1,添加依赖
//Paging的依赖
implementation "android.arch.paging:runtime:1.0.0"
//Paging对RxJava2的原生支持
implementation "android.arch.paging:rxjava2:1.0.0-rc1"
2,创建数据源
PositionalDataSource: 主要用于加载数据可数有限的数据。比如加载本地数据库,这种情况下用户可以通过比如说像通讯录按姓的首字母查询的情况。能够跳转到任意的位置。 ItemKeyedDataSource:主要用于加载逐渐增加的数据。比如说网络请求的数据随着不断的请求得到的数据越来越多。然后它适用的情况就是通过N-1item的数据来获取Nitem数据的情况。 PageKeyedDataSource:这个和ItemKeyedDataSource有些相似,都是针对那种不断增加的数据。这里网络请求得到数据是分页的。
DataSource.Factory
这个接口的实现类主要是用来获取的DataSource的。
PagedListAdapter
这个Adapter继承自RecyclerView.Adapter。如果要使用Paging,就需要让实现的RecyclerView的Adapter继承自PagedListAdapter。这个抽象类实现关于PagedList相关的东西。
LivePagedListBuilder
通过这个类来生成对应的PagedList
3,创建ViewModel对象
我们知道在jetpack中推荐每个页面持有一个VIewModel对象,用来保证数据的正确性以及避免其他问题。但是这里对ViewModel不做具体介绍。
4,Recyclerview
在使用PagedListAdapter时,我们需要注意的是,PagedListAdapter内部默认实现DiffUtil来进行数据的差量计算,所以我们在构造方法里面传递一个DiffUtil.ItemCallback。
这里几种介绍一下是DiffUtil,用它来做什么?为什么会出现?
虽然 RecyclerView 提供的局部更新的方法,看似非常的好用,但是实际上,其实并没有什么用。在实际开发中,最方便的做法就是无脑调用 notifyDataSetChanged(),用于更新 Adapter 的数据集。虽然 notifyDataSetChanged() 有一些缺点:不会触发 RecyclerView 的局部更新的动画。性能低,会刷新整个 RecyclerView 可视区域。但是真有需要频繁刷新,前后两个数据集的场景。方案一:使用一个 notifyDataSetChanged() 方法。方案二:自己写一个数据集比对方法,然后去计算他们的差值,最后调用对应的方法更新到 RecyclerView 中去。我这么懒,如果不是必要,当然是会选 方案一 了。毕竟和之前 ListView 的时候,也没有更差了。Google 显然也发现了这个问题,所以 DiffUtil 被发布了 。
就像前面说的,DiffUtil 就是为了解决这个痛点的。它能很方便的对两个数据集之间进行比对,然后计算出变动情况,配合 RecyclerView.Adapter ,可以自动根据变动情况,调用 Adapter 的对应方法。
DiffUtil 在使用起来,主要需要关注几个类: DiffUtil.Callback:具体用于限定数据集比对规则。 DiffUtil.DiffResult:比对数据集之后,返回的差异结果。
1,Diffutil.Callback
DiffUtil.Callback 主要就是为了限定两个数据集中,子项的比对规则。毕竟开发者面对的数据结构多种多样,既然没法做一套通用的内容比对方式,那么就将比对的规则,交还给开发者来实现即可。
在 Callback 中,其实只需要实现 4 个方法:
getOldListSize():旧数据集的长度。
getNewListSize():新数据集的长度
areItemsTheSame():判断是否是同一个Item。
areContentsTheSame():如果是通一个Item,此方法用于判断是否同一个 Item 的内容也相同。
后两个方法,主要是为了对应多布局的情况产生的,也就是存在多个 viewType 和多个 ViewHodler 的情况。首先需要使用 areItemsTheSame() 方法比对是否来自同一个 viewType(也就是同一个 ViewHolder ) ,然后再通过 areContentsTheSame() 方法比对其内容是否也相等。
其实 Callback 还有一个 getChangePayload() 的方法,它可以在 ViewType 相同,但是内容不相同的时候,用 payLoad 记录需要在这个 ViewHolder 中,具体需要更新的View。areItemsTheSame()、areContentsTheSame()、getChangePayload() 分别代表了不同量级的刷新。
首先会通过 areItemsTheSame() 判断当前 position 下,ViewType 是否一致,如果不一致就表明当前 position 下,从数据到 UI 结构上全部变化了,那么就不关心内容,直接更新就好了。如果一致的话,那么其实 View 是可以复用的,就还需要再通过 areContentsTheSame() 方法判断其内容是否一致,如果一致,则表示是同一条数据,不需要做额外的操作。但是一旦不一致,则还会调用 getChangePayload() 来标记到底是哪个地方的不一样,最终标记需要更新的地方,最终返回给 DiffResult 。当然,对性能要是要求没那么高的情况下,是可以不使用 getChangedPayload() 方法的。
2,DiffUtil.DiffResult
DiffUtil.DiffResult 其实就是 DiffUtil 通过 DiffUtil.Callback 计算出来,两个数据集的差异。它是可以直接使用在 RecyclerView 上的。如果有必要,也是可以通过实现 ListUpdateCallback 接口,来比对这些差异的。
3,使用DiffUtil
介绍了 Callback 和 DiffResult 之后,其实就可以正常使用 DiffUtil 来进行数据集的比对了。在这个过程中,其实真的很简单,只需要调用两个方法:
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true);diffResult.dispatchUpdatesTo(mAdapter);
calculateDiff 方法主要是用于通过一个具体的 DiffUtils.Callback 实现对象,来计算出两个数据集差异的结果,得到 DiffUtil.DiffResult 。而 calculateDiff 的另外一个参数,用于标记是否需要检测 Item 的移动。
DiffUtil 使用的是 Eugene Myers 的差别算法,这个算法本身是不检查元素的移动的。也就是说,有元素的移动它也只是会先标记为删除,然后再标记插入。而如果需要计算元素的移动,它实际上也是在通过 Eugene Myers 算法比对之后,再进行一次移动检查。所以,如果集合本身已经排序过了,可以不进行移动的检查。
而 dispatchUpdatesTo() 就是将这个数据集差异的结果,通过 Adapter 更新到 RecyclerView 上面。实际上 dispatchUpdatesTo(Adapter) ,也是使用的 ListUpdateCallback 这个接口,在其中获得差异,然后调用 Adapter 的对应方法。
Google 官方同时也指出,如果是对大数据集的比对,最好是放在子线程中去完成计算,也就是其实是存在堵塞 UI 的情况的。所以如果你遇见了使用 DiffUtil 之后,每次刷新有卡顿的情况,可以考虑是否数据集太大,是否应该在子线程中完成计算。
5,Activity中实战
Jetpack是Google官方推荐的一种App架构方案,在这种结构下,会避免很多的问题,比如说内存泄漏和空指针异常等等。之所以使用这种方案来架构App,不是我们吃饱撑的没事干,而是为了解决传统架构方案下不能解决的问题。当然可能还有其他的第三方解决方案,但是肯定不比Jetpack权威,毕竟Google爸爸亲自设计的。
横批:早日脱单
上联:愿诸君,添好运,代码无错,奖金成摞,
下联:祝各位,发大财,身强体壮,毛发挺旺。