New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Android官方架构组件Paging-Ex:列表状态的响应式管理 #2
Comments
交流能够让每个人都集思广益,因此如果有意见或者建议,欢迎大家评论 |
�您好,您的文章写得很好,我现在遇到一个这样的问题,我是做tv开发的使用了android官方出的leanback库,这个库中的adapter不是recyclerView的adapter,但是使用paging必须使用PagedListAdapter,这样就有问题,在不拷贝全部源码的情况下,不知道有没有别的方式能解决这个问题,期待您的回复 |
你好,leanback这个库(或者说AndroidTV的开发)我不是很熟,但是分页的话,可以使用别的库,没必要非要用 |
@qingmei2 thx |
@erehmi 多谢指教 😄 |
感覺目前paging lib比較大的問題在於修改資料。如文章所說,許多輕量級的行為必須即時更新上去。但是ui必定綁在db層的update上。個人想到一個作法是在db新增Like=pending之類的值來保持狀態同步,當request callback成功後再來更新為Like=true. |
目前看到推薦的做法為自己弄個memory repository layer來cache這些資料。當資料變動時invalidate pagedList讓他回memory repository去拿。此做法要注意loadInit必須能讀到指定page不然會跳回第一頁。或者直接回全部資料讓DiffCallback去處理。 |
内存中维护一个 |
@NickJian +1 |
@qingmei2 |
另外一个思路,重写 RxPagedListBuilder(or LivePagedListBuilder):
这样的话,可以不用新增Memory Repository Layer,避免了多余的内存开销。 |
除了大吼一声牛逼就没啥可说的了!!!非常有价值的一篇文章! |
也就是思路是 |
概述
Paging
是Google
在2018年I/O大会上推出的适用于Android
原生开发的分页库,随着越来越多的开发者着手使用Paging
,越来越多的问题暴露出来,最直接的一个问题是:这样的需求随处可见,比如
侧滑删除
、为评论点赞
等等:本文将阐述:如何管理
Paging
分页列表的 状态,为何这样设计,以及设计的过程。列表的状态问题
和市面上其它热门的分页库相比,
Paging
最大的亮点在于其 将列表分页加载的逻辑作为回调函数封装入DataSource
中,开发者在配置完成后,无需通过代码手动控制分页的加载,列表会 自动加载 下一页数据并展示。这种便利意味着开发者不需要自己持有 数据源 ,大多数时候这使得开发流程更加便利,但总有偶然,比如这样一个界面:
这种需求屡见不鲜,其本质是,列表本身展示服务端返回的列表数据之外,还需要 本地控制额外的状态。
什么叫 额外的状态 ? 我们先用简单的一张图展示没有额外状态的情形,这时,列表的所有UI元素都从服务端获取:
现在我们将上文
Gif
中的点赞效果也通过一张图表示:读者可能还未认识到两种业务场景之间的差异性:对于列表的初始化来讲,所有UI元素都被服务端返回的数据渲染,每条评论是否已经被点赞,服务端都通过
Comment
进行了描述。需要注意的是,在某一刻,用户发现某个评论非常有趣,因此他选择对该评论进行了点赞的操作。
在业务代码中,我们需要向服务端
POST
一个点赞的请求,服务端返回了一个200的成功码,但问题来了,接下来我们 如何让列表中的那条评论状态发生变化(即点赞的icon由灰色变成绿色高亮,已告知用户点赞成功)?这就引发了文章最开始的那个问题,当列表的状态发生了变更,如何管理并更新列表?
方案1:再次刷新请求接口
最简单的方案是再次请求API,每当列表状态发生了变更,重新拉取评论列表,服务端返回的最新数据中,该评论自然已经被点赞了(即列表正确进行了更新)。
读者应该清楚,该方案实际并不可行,原因有二:
Paging
是一个分页列表,而刷新请求行为对于分页列表来说,是一个不符合产品预期的行为(比如,我的点赞操作是针对第5页的某个评论执行的,产品的设计不可能允许每次点赞都重置为列表的第一页数据,这意味着极度糟糕的用户体验)。现在我们理解了 每当列表状态发生了变更就刷新接口 并非良策,因为这种通过 远程重新拉取数据源 更新UI的方式成本太高了。
方案2:额外维护一个状态的列表
大概思路是在内存中为
RecyclerView
维护一个额外的List
,用于一一映射对应position
的Item
状态:通过在内存中维护这样一个
List
,的确可以实现需求,但读者需要认识到的是,Paging
分页库本身最大的优点便是 随着列表的滚动自动加载分页数据,每次分页的行为开发者并不需要手动配置,并通过调用类似notifyItemRangeInserted()
的方法更新UI。很显然,每当分页数据获取后,开发者依然需要手动维护这个额外状态的
List
——方案2和选择使用Paging
的初衷背道而驰,因此它并非最优先考虑的方案。库本身设计的问题?
现在问题是,既不能通过 服务端 作为数据源,也不能在 内存中 额外维护一个状态的列表, 读者难免会质疑
Paging
库本身设计的问题。事实上该问题已经在Github的这个 issue 中进行了讨论,
Google
的工程师的回复是:显然,
Paging
考虑到了更多,和市面上 什么都能做 的框架相比,它 敢于收紧开发者API的调用权限,在开发者们发挥更多奇思妙想之前,将其紧紧束缚到了可控制的范围之内,这也是笔者非常推崇Paging
的原因之一。那么我们该如何处理我们的业务?此时引入一个新的角色似乎是一个不错的选择,那就是 持久层(即缓存)。
通过架构解决业务问题
综上所述,对于分页列表的状态管理问题,需要做到的是:
List
交给Paging
去进行分页加载并渲染(不应在内存中手动维护一个额外状态的列表);因此,我们需要一个 中间件 进行业务的调度——在需要刷新整个数据源的时候(比如用户的下拉刷新操作),从服务端拉取数据;在不需要繁重的操作时(比如用户针对某个评论进行点赞),仅仅需要针对单个数据源进行简单的修改。
这已经不单单是业务业务的问题,并且涉及到了项目本身的架构,接下来, 持久层 (即本地缓存)闪亮登场。
1.用持久层作为唯一的数据源
为什么要为项目的架构额外添加一个持久层?事实上,随着项目体系的日益庞大,数据库是终究需要添加进入项目中的,因此,在设计项目的架构之前,提前将数据库的框架配置进来是一个不错的选择——未雨绸缪总不是坏事。
以列表的渲染为例,让我们来看看项目之前的结构:
回到本文,对于
Paging
来讲,我们并无法直接获取数据源,因此对于列表状态的管理,我们需要额外的角色帮助,那就是本地的持久化缓存。让我们看看添加了持久层之后的结构:
添加了缓存之后,每当我们尝试初始化一个分页列表,框架会从服务器拉取数据,之后数据被存储到了
Room
中。请注意!
Paging
原生提供了对Room
数据库框架的支持,因此它总是可以第一时间响应到数据库中数据的变化,并自动渲染在UI上。现在,我们将 请求服务器API 和 数据的渲染 两者通过持久层进行了隔离,对于
RecyclerView
来说,持久层是唯一的数据源,即:现在列表的显示和服务端的请求已经 完全无关 了,读者也许会有这样的疑问——这样做的好处是什么?
2.列表状态的管理
现在我们回到文中最初的问题,如何管理列表的状态?
对于一个拥有复杂状态的分页列表,无论是 服务端 作为数据源,还是在 内存中 额外维护一个状态列表,都不是很好的选择;而现在我们加入了
Room
,并作为列表唯一的数据源,局势发生了怎样微妙的变化呢?让我们来看看加入了持久层之后,下拉刷新的逻辑发生了怎样的变化:
Paging
会自动响应到数据的变化,因为没有了数据,所以Paging
会自动向服务器请求数据;Paging
会再次响应到数据库的变化,并将最新的数据渲染到UI上。看起来逻辑复杂了很多,实际上读者需要明确的是,步骤2、3、4都是我们作为开发者在初始化
Paging
时就配置好的,因此如果用户需要刷新页面,只需要进行第一步的操作即可,即类似这样的一行代码:现在我们将整个流程中,
Paging
自动执行的步骤用紫色标记出来:瞧,除了我们手动执行的逻辑,所有流程都交给了
Paging
去 响应式 地执行。我们总是下意识认为复杂的业务逻辑用过程式的编码更容易实现,
Paging
用事实证明了并非如此——如果说项目中的某个页面追加了下拉刷新的需求,过程式的编码也许会花费更多的时间,并且代码也许会更分散、啰嗦且易出错。3.更灵活、且可高度扩展
接下来分析的是,对分页列表点赞这种相对 轻量级的行为 又该如何处理?
答案呼之欲出, 我们依然用熟悉的流程图表示代码的执行步骤:
即使是复杂的状态,在这种模式下也不再是难题:首先,我们将数据库对应表中对应评论的
isLike
(是否被点赞)设置为true
:与此同时,我们也向服务器请求接口,告知评论被用户点赞:
当数据库中数据发生了变更,
Paging
仍然会响应到数据的更新,并第一时间更新了UI,同时我们也向服务器发起了请求,一个完整的 点赞 操作相关的业务代码实现完毕。有了持久层作为中间件,代码组织的灵活性大大提升,同时也具备了更高的扩展性。列表状态的管理不再是问题,诸如 点赞 、 下拉刷新 、 侧滑删除 等等等等,都可以通过对持久层的数据源进行修改,
paging
总是可以第一时间自动响应到变更并更新UI。也正如
Room
官方文档第一句话所说的,对于Paging
分页列表(对app也一样)复杂的状态的展示和管理,开发者应该 将缓存作为列表的唯一真实的数据源:代码示例?
如读者所看到的,本文尽量避免展示大篇幅的业务代码,原因有二:
本文的目的是阐述笔者遇到问题的解决步骤和思路,读者了解整体的方案之后,可以根据实际项目进行技术选型。
当然,如果有相关的疑惑,欢迎参考下面两个项目的具体实现,这是笔者基于上文的
Paging
+Room
组件,实现了一个简单的Github
的客户端,本文不细述。1.
MVVM
架构的Sample: https://github.com/qingmei2/MVVM-Rhine2.
MVI
架构的Sample:https://github.com/qingmei2/MVI-Rhine系列文章
关于我
Hello,我是却把清梅嗅,如果您觉得文章对您有价值,欢迎 ❤️,也欢迎关注我的博客或者Github。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?
The text was updated successfully, but these errors were encountered: