Open qingmei2 opened 5 years ago
交流能够让每个人都集思广益,因此如果有意见或者建议,欢迎大家评论
您好,您的文章写得很好,我现在遇到一个这样的问题,我是做tv开发的使用了android官方出的leanback库,这个库中的adapter不是recyclerView的adapter,但是使用paging必须使用PagedListAdapter,这样就有问题,在不拷贝全部源码的情况下,不知道有没有别的方式能解决这个问题,期待您的回复
@chengfangpeng
你好,leanback这个库(或者说AndroidTV的开发)我不是很熟,但是分页的话,可以使用别的库,没必要非要用Paging
进行分页哈。
@qingmei2 thx
@erehmi
多谢指教 😄
感覺目前paging lib比較大的問題在於修改資料。如文章所說,許多輕量級的行為必須即時更新上去。但是ui必定綁在db層的update上。個人想到一個作法是在db新增Like=pending之類的值來保持狀態同步,當request callback成功後再來更新為Like=true.
另一個問題是不需要做persistent的資料難以修改。依照預設作法當你資料修改,是必須拿新的dataSource/PagedList來讀取。假設你直接call api,會對server造成過大負擔。不知道當不做persistent狀況下如何處理比較好?
另一個問題是不需要做persistent的資料難以修改。依照預設作法當你資料修改,是必須拿新的dataSource/PagedList來讀取。假設你直接call api,會對server造成過大負擔。不知道當不做persistent狀況下如何處理比較好?
目前看到推薦的做法為自己弄個memory repository layer來cache這些資料。當資料變動時invalidate pagedList讓他回memory repository去拿。此做法要注意loadInit必須能讀到指定page不然會跳回第一頁。或者直接回全部資料讓DiffCallback去處理。 @qingmei2 不知道為什麼有收到你的回覆通知但這邊看不到?
@NickJian
内存中维护一个memory repository layer
感觉可行,但本质和Room
的persistent layer
似乎没什么区别? 我没有做过这样的尝试,因为不舍得放弃Room
原生的支持,请问你这种方案有相关的demo源码吗,我有空拜读一下。 😄
@NickJian +1
@qingmei2
参考Room
实现一套Memory Repository Layer
,支持增删改查,支持数据可订阅,最终实现的效果应该和Room
是一致的。只是使用PageKeyedDataSource
来做分页数据加载的话,可能还是会有问题,因为DataSource
在invalidate()
之后会重新创建,但是新的DataSource
无法获取到初始Key
和初始加载位置(即最近加载的PagedList的最后加载的Key
和加载位置),这就会导致页面无法恢复加载数据的位置,从而导致从第1页开始重新加载数据。然而ItemKeyedDataSource
&PositionalDataSource
在重新创建时,是会传入上述参数的。
@qingmei2 我是在這邊看到這做法的。你只需要改寫DataSource.factory讓他回傳memory中object就可以了。 個人感覺這做法還蠻不直覺的...
另外一个思路,重写 RxPagedListBuilder(or LivePagedListBuilder):
这样的话,可以不用新增Memory Repository Layer,避免了多余的内存开销。
除了大吼一声牛逼就没啥可说的了!!!非常有价值的一篇文章!
也就是思路是 把接口请求的列表数据都放到db中吗? 这样的话,什么时候清除db中的数据呢?
概述
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。
如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?