我该如何控制列表额外的状态(包括修改、增加或者删除)?
事实上该问题已经在Github的这个 issue 中进行了讨论,Google
的工程师的回复是:
从技术的角度而言 ,我们可以创建一个允许部分更改数据源的API,但之后我们需要记录这些改动并在主线程上重新传递给列表。这种方法的问题在于,如果你有一个已停止的
RecyclerView
(也就是后堆栈),它将不会(也不应该)接收任何更新,因此PagedList
将保留这个可能很长的数据列表并重新应用于主线程上的每个观察者。
这使问题变得非常复杂,这就是我们使用单个列表的原因。
显然,Paging
考虑到了更多,和市面上 什么都能做 的框架相比,它 敢于收紧开发者API的调用权限,在开发者们发挥更多奇思妙想之前,将其紧紧束缚到了可控制的范围之内,这也是笔者非常推崇Paging
的原因之一。
那么我们该如何处理我们的业务?此时引入一个新的角色似乎是一个不错的选择,那就是 持久层(即缓存)。
通过架构解决业务问题
综上所述,对于分页列表的状态管理问题,需要做到的是:
- 1.将一个单独的
List
交给Paging
去进行分页加载并渲染(不应在内存中手动维护一个额外状态的列表); - 2.不应该每次都通过重的操作刷新数据源(比如网络请求刷新接口)。
因此,我们需要一个 中间件 进行业务的调度——在需要刷新整个数据源的时候(比如用户的下拉刷新操作),从服务端拉取数据;在不需要繁重的操作时(比如用户针对某个评论进行点赞),仅仅需要针对单个数据源进行简单的修改。
这已经不单单是业务业务的问题,并且涉及到了项目本身的架构,接下来, 持久层 (即本地缓存)闪亮登场。
1.用持久层作为唯一的数据源
Android
平台的数据库框架有很多种,本文以官方的架构组件Room
为例。
为什么要为项目的架构额外添加一个持久层?事实上,随着项目体系的日益庞大,数据库是终究需要添加进入项目中的,因此,在设计项目的架构之前,提前将数据库的框架配置进来是一个不错的选择——未雨绸缪总不是坏事。
以列表的渲染为例,让我们来看看项目之前的结构:
回到本文,对于Paging
来讲,我们并无法直接获取数据源,因此对于列表状态的管理,我们需要额外的角色帮助,那就是本地的持久化缓存。
让我们看看添加了持久层之后的结构:
添加了缓存之后,每当我们尝试初始化一个分页列表,框架会从服务器拉取数据,之后数据被存储到了Room
中。
请注意!Paging
原生提供了对Room
数据库框架的支持,因此它总是可以第一时间响应到数据库中数据的变化,并自动渲染在UI上。
现在,我们将 请求服务器API 和 数据的渲染 两者通过持久层进行了隔离,对于RecyclerView
来说,持久层是唯一的数据源,即:
列表只反应了数据库的变更。
现在列表的显示和服务端的请求已经 完全无关 了,读者也许会有这样的疑问——这样做的好处是什么?
2.列表状态的管理
现在我们回到文中最初的问题,如何管理列表的状态?
对于一个拥有复杂状态的分页列表,无论是 服务端 作为数据源,还是在 内存中 额外维护一个状态列表,都不是很好的选择;而现在我们加入了Room
,并作为列表唯一的数据源,局势发生了怎样微妙的变化呢?
让我们来看看加入了持久层之后,下拉刷新的逻辑发生了怎样的变化:
- 1.下拉刷新意味着我们需要重置数据,因此我们手动清除了数据库内对应表中的数据;
- 2.当表中数据被清空时,
Paging
会自动响应到数据的变化,因为没有了数据,所以Paging
会自动向服务器请求数据; - 3.数据返回后,会再次将数据存储到数据库中;
- 4.这时
Paging
会再次响应到数据库的变化,并将最新的数据渲染到UI上。
看起来逻辑复杂了很多,实际上读者需要明确的是,步骤2、3、4都是我们作为开发者在初始化Paging
时就配置好的,因此如果用户需要刷新页面,只需要进行第一步的操作即可,即类似这样的一行代码:
// 刷新操作,仅需清除表内的列表数据
fun swipeRefresh() {
// 运行一个事务
db.runInTransaction {
// 清除列表数据
db.getDao().clearDataList()
}
}
现在我们将整个流程中,Paging
自动执行的步骤用紫色标记出来:
瞧,除了我们手动执行的逻辑,所有流程都交给了Paging
去 响应式 地执行。
我们总是下意识认为复杂的业务逻辑用过程式的编码更容易实现,Paging
用事实证明了并非如此——如果说项目中的某个页面追加了下拉刷新的需求,过程式的编码也许会花费更多的时间,并且代码也许会更分散、啰嗦且易出错。
3.更灵活、且可高度扩展
接下来分析的是,对分页列表点赞这种相对 轻量级的行为 又该如何处理?
答案呼之欲出, 我们依然用熟悉的流程图表示代码的执行步骤:
即使是复杂的状态,在这种模式下也不再是难题:首先,我们将数据库对应表中对应评论的isLike
(是否被点赞)设置为true
:
// 1.对本地的评论数据点赞
fun likeCommentLocal(comment: Comment) {
// 更新评论
comment.isLike = true
// 将评论更新到数据库中
db.runInTransaction {
db.getDao().updateLikeComment(o)
}
}
与此同时,我们也向服务器请求接口,告知评论被用户点赞:
// 2.对评论点赞
fun likeCommentRemote(commentId: String) {
service.likeComment(commentId)
// …
}
当数据库中数据发生了变更,Paging
仍然会响应到数据的更新,并第一时间更新了UI,同时我们也向服务器发起了请求,一个完整的 点赞 操作相关的业务代码实现完毕。
有了持久层作为中间件,代码组织的灵活性大大提升,同时也具备了更高的扩展性。列表状态的管理不再是问题,诸如 点赞 、 下拉刷新 、 侧滑删除 等等等等,都可以通过对持久层的数据源进行修改,paging
总是可以第一时间自动响应到变更并更新UI。
也正如Room
官方文档第一句话所说的,对于Paging
分页列表(对app也一样)复杂的状态的展示和管理,开发者应该 将缓存作为列表的唯一真实的数据源:
This cache, which serves as your app’s single source of truth.
代码示例?
如读者所看到的,本文尽量避免展示大篇幅的业务代码,原因有二:
- 1.这会破坏文章整体思路的完整性,没有人喜欢阅读大篇幅、连续的代码片段;
- 2.实际开发中,项目的业务不同、架构选型不同,代码的实现方式也不尽相同,因此业务级别的代码展示没有意义。
比如,对于持久层框架的选型,
Room
、GreenDao
、DBFlow
都是非常优秀的框架,对于业务代码的实现,RxJava
、LiveData
、协程都是优秀的实现方案…
本文的目的是阐述笔者遇到问题的解决步骤和思路,读者了解整体的方案之后,可以根据实际项目进行技术选型。
当然,如果有相关的疑惑,欢迎参考下面两个项目的具体实现,这是笔者基于上文的Paging
+Room
组件,实现了一个简单的Github
的客户端,本文不细述。
1.MVVM
架构的Sample: github.com/qingmei2/MV…
2.MVI
架构的Sample:github.com/qingmei2/MV…
系列文章
争取打造 Android Jetpack 讲解的最好的博客系列:
Android Jetpack 实战篇:
关于我
Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
学习分享
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021最新上万页的大厂面试真题
七大模块学习资料:如NDK模块开发、Android框架体系架构…
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
开发几年,不知道如何进阶更进一步,比较迷茫。
**第三,**到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!