反思|Android 列表分页组件Paging的设计与实现:系统概述
反思|Android 列表分页组件Paging的设计与实现:架构设计与原理解析
以上两篇文章将对Paging分页组件进行了系统性的概述,笔者强烈建议 读者将以上两篇文章作为学习 Paging 阅读优先级 最高 的学习资料,所有其它的Paging中文博客阅读优先级都应该靠后。
本文及相关引申阅读:
Android官方架构组件Paging:分页库的设计美学
Android官方架构组件Paging-Ex:为分页列表添加Header和Footer
Android官方架构组件Paging-Ex:列表状态的响应式管理
概述
Paging
是Google
在2018年I/O大会上推出的适用于Android
原生开发的分页库,随着越来越多的开发者着手使用Paging
,越来越多的问题暴露出来,最直接的一个问题是:
如何管理列表额外的状态?
这样的需求随处可见,比如 侧滑删除
、为评论点赞
等等:
本文将阐述:如何管理Paging
分页列表的 状态,为何这样设计,以及设计的过程。
列表的状态问题
和市面上其它热门的分页库相比,Paging
最大的亮点在于其 将列表分页加载的逻辑作为回调函数封装入 DataSource
中,开发者在配置完成后,无需通过代码手动控制分页的加载,列表会 自动加载 下一页数据并展示。
这种便利意味着开发者不需要自己持有 数据源 ,大多数时候这使得开发流程更加便利,但总有偶然,比如这样一个界面:
这种需求屡见不鲜,其本质是,列表本身展示服务端返回的列表数据之外,还需要 本地控制额外的状态。
什么叫 额外的状态 ? 我们先用简单的一张图展示没有额外状态的情形,这时,列表的所有UI元素都从服务端获取:
现在我们将上文Gif
中的点赞效果也通过一张图表示:
读者可能还未认识到两种业务场景之间的差异性:对于列表的初始化来讲,所有UI元素都被服务端返回的数据渲染,每条评论是否已经被点赞,服务端都通过Comment
进行了描述。
需要注意的是,在某一刻,用户发现某个评论非常有趣,因此他选择对该评论进行了点赞的操作。
在业务代码中,我们需要向服务端POST
一个点赞的请求,服务端返回了一个200的成功码,但问题来了,接下来我们 如何让列表中的那条评论状态发生变化(即点赞的icon由灰色变成绿色高亮,已告知用户点赞成功)?
这就引发了文章最开始的那个问题,当列表的状态发生了变更,如何管理并更新列表?
方案1:再次刷新请求接口
最简单的方案是再次请求API,每当列表状态发生了变更,重新拉取评论列表,服务端返回的最新数据中,该评论自然已经被点赞了(即列表正确进行了更新)。
读者应该清楚,该方案实际并不可行,原因有二:
- 成本太高:某些操作对于用户来说,应该是非常 轻量级 的(比如点赞),他们甚至希望这些操作能够 立即被响应 在UI上 ,而请求API并刷新列表这一个过程太重了,即使不考虑服务器的负担,对于用户来说,UI的刷新需要数秒的等待也是非常糟糕的体验。
- 不符合逻辑:我们更需要注意的是,
Paging
是一个分页列表,而刷新请求行为对于分页列表来说,是一个不符合产品预期的行为(比如,我的点赞操作是针对第5页的某个评论执行的,产品的设计不可能允许每次点赞都重置为列表的第一页数据,这意味着极度糟糕的用户体验)。
现在我们理解了 每当列表状态发生了变更就刷新接口 并非良策,因为这种通过 远程重新拉取数据源 更新UI的方式成本太高了。
方案2:额外维护一个状态的列表
大概思路是在内存中为RecyclerView
维护一个额外的List
,用于一一映射对应position
的Item
状态:
class CommentPagedAdapter(
private val likedList: ArrayList
)
通过在内存中维护这样一个List
,的确可以实现需求,但读者需要认识到的是,Paging
分页库本身最大的优点便是 随着列表的滚动自动加载分页数据,每次分页的行为开发者并不需要手动配置,并通过调用类似notifyItemRangeInserted()
的方法更新UI。
很显然,每当分页数据获取后,开发者依然需要手动维护这个额外状态的List
——方案2和选择使用Paging
的初衷背道而驰,因此它并非最优先考虑的方案。
库本身设计的问题?
现在问题是,既不能通过 服务端 作为数据源,也不能在 内存中 额外维护一个状态的列表, 读者难免会质疑Paging
库本身设计的问题。
我该如何控制列表额外的状态(包括修改、增加或者删除)?
事实上该问题已经在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()
}
}
小福利:
在当下这个碎片化信息环境的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021大厂最新Android面试真题解析
各个模块学习视频:如数据结构与算法
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!点赞+评论即可获得!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
到真正的技术提升。**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!