往期回顾,专栏目录,更新动态,优惠政策,版权须知
温馨提示:如这是第一次接触《重学安卓》,可通过上述链接快速了解《重学安卓》专栏,获取 专栏目录、试读内容、更新动态 和 发展状况。
截至目前,专栏已对 体系化文章 实施 3310 余次修订,数十位群友告诉我,受专栏启发 亦开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创开源库 和 提供内推机会,订阅后可随时进群交流。
·
Note 2023.5.19 新版本提示:
本文最初发行于 2020.7.15,在此期间,我们从未停下分享和交流的脚步,
为此,一方面,我们不断为《LiveData 数据倒灌》篇提供加餐和翻新,另一方面,不断萌生和沉淀新的见解,
经过多年沉淀,本文迎来了 “重制版” ——《通俗易懂 “响应式框架” 使用避坑与封装思路》,通过 “循序渐进、言简意赅” 的叙述方式,重新阐述完整的理解,
建议以 “重制版” 内容为准,而打满细节补丁的本文,则可作为 “工具书” 来查阅和参考。
·
注:本文以
《重学安卓:为你还原一个真实的 Jetpack Lifecycle》
《重学安卓:LiveData 鲜为人知 身世背景 和 独特使命》
《重学安卓:页面开发 左右逢源 Jetpack ViewModel》
《重学安卓:耳目一新 Jetpack MVVM 精讲》 及
《GitHub:Jetpack MVVM Best Practice》 作为前置知识,假设小伙伴已完整查阅并吸收上述内容。
前言
上周有位字节工作小伙伴私信感叹:“文章相见恨晚,恨晚呐”。
据该小伙伴描述,他早在 2017 年便开始在项目中尝试 AAC(Android Architecture Components),原以为好日子即将开始,没想到被 LiveData “数据倒灌” 问题折磨到怀疑人生。
彼时了解 Jetpack 乃至爬坑 LiveData 开发者并不多,网上这方面资料略少,且多数开发者自学一门新技术 都是首选官方文档,
而这一次问题根源就在于,官方文档 亲自推荐通过 SharedViewModel + LiveData 组合解决页面通信问题,却在相当长时间里,都未亲自验证并解决 该方案于场景下存在的致命缺陷。
—— 架构组件的存在,本是为规避不可预期错误,没想到却因这徒添不可预期错误。
姗姗来迟笼统描述
与此类现象相关解决方案,最早是 2018 年中旬才在网上陆续刊登。
比如国外的:LiveData with SnackBar, Navigation and other events
及国内的:Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus
但无论哪一种,都未就此现象背景做过 “因地制宜” 概括,而只是笼统用 “粘性事件” 来表达,也因此多数新上手 Jetpack 架构组件小伙伴 根本无从理解这个 “粘性事件” 到底意味着什么,更不曾料想到 当下正困扰着自己的,就是这个。
因此,本文目标即是提供 “数据倒灌” 现象 独家概括、缘由分析,及趋近完美解决方案。
看完后,如你对 “数据倒灌” 概念有了印象、未来在遇到类似状况时能想起、并重新翻出和回溯这篇文章,那我愿望也就达到。
文章目录一览
- 前言
- 姗姗来迟笼统描述
- “数据倒灌” 由来
- Note 2022.11.09 加餐:
- LiveData 漏洞拆解及改善建议
- 为何会有数据倒灌
- Note:2020.07.17 加餐:
- 图文解析数据倒灌事发场景
- 为何非要通过 ViewModel + LiveData 处理页面通信?
- 为何通过全局 ViewModel 而非静态单例?
- 为何不用 LiveDataBus?
- 最新 Result API 如何?
- Event 事件包装器、反射方式、SingleLiveEvent 分别存在什么问题?
- UnPeekLiveData 是如何解决这些问题?
- GitHub & Maven 依赖
- 综上
- Note 2022.06.13 加餐:
- UnPeek-LiveData v7.6 提供 MutableResult 类
- Note 2022.07.12 加餐:
- 通过 MVI-Dispatcher 彻底消除 setValue 滥用
- 历史版本更新说明和思路分享
- Note 2020.07.21 加餐:
- UnPeekLiveData 增加一层 ProtectedUnPeekLiveData 设计缘由解析
- Note 2020.10.15 加餐:
- 升级到 UnPeekLiveData v4.0 享用 “更快更稳” 防倒灌机制
- Note 2021.04.22 加餐:
- 升级到 UnPeekLiveData v5.0 获得更好内存性能和更友好 API 访问
- Note 2021.06.18 加餐:
- 是代码复杂度更小 UnPeekLiveData v6.0 设计
- Note 2021.07.19 加餐:
- 是代码复杂度进一步减小 UnPeekLiveData v6.1
- Note 2021.08.10 加餐:
- 分享一份 v6.1 源码简化版,方便对核心 “防倒灌” 机制理解
- Note 2021.08.12 加餐:
- 是内存管理效率进一步优化 UnPeekLiveData v7.0
- Note 2021.08.21 加餐:
- 分享一份 v7.2 源码完整版,是简练完整防倒灌设计
“数据倒灌” 由来
今天提到 “数据倒灌” 一词,缘于本人为方便理解和记忆 “再入二级页面时收到旧数据推送” 情况,而于 2019 年 自创并在网上传播的 关于此类现象概括。
遗憾的是,官方文档 至今 (截至 2020.7.15) 都未意识到 并就该致命问题做出警告。
Note 2022.11.09 加餐:
LiveData 漏洞拆解及改善建议
根据《Jetpack MVVM 分层设计解析》 篇的分析易知,响应式编程便于单元测试,但存在漏洞,LiveData 不幸继承了该漏洞,造成包括所谓 “数据倒灌” 在内的一系列问题。
如您在接触 LiveData 框架前,已接触并熟悉 RxJava 和响应式编程,那么建议结合《Jetpack MVVM 分层设计解析》 篇来理解造成 LiveData 漏洞其背后深层次的原因。
如您此前未接触过 “响应式编程”,也不感兴趣了解,只是想解决粘性问题,那么继续往下阅读文本也无妨。
为何会有数据倒灌
前言中已经提到,新上手小伙伴们依赖官方文档,而官方文档给出 “页面间通信” 场景解决方案容易造成误解,且存在致命缺陷 ——
注:根据文档原意,该组合仅适用于 “splitView 场景”,也即左右分栏场景。但现实中这只是低频场景,开发者容易误用在一二级页面跳转的高频场景:
一方面,用于通信的 LiveData 是被托管在 Activity / Application 级作用域 SharedViewModel 中,于是 LiveData 生存期长于任何一个 Fragment(假设通信双方是 Fragment):当二级 Fragment 出栈时,LiveData 实例仍存在。
另一方面,LiveData 本身被设计为粘性事件,也即,一旦 LiveData 持有数据,那么在观察者订阅该 LiveData 时,会被推送最后一次数据。
该设定本身也符合 LiveData 常用场景(返回 UiState) —— 例如 页面重建时,自动推送最后一次数据,而不必重新向后台请求。
只不过,该特性在 “页面间通信” 场景(返回 UiEvent)下,便是致命灾难 ——
新上手小伙伴完全意料不到,还会有这样 “预期外” 状况在等着自己,于是成就读者群 “高频提问和解答” TOP 2。
不知我这样说,有无说明白?——
此处举一个存在 “数据倒灌” 隐患例子:
Note 2021.8.16:重要提示,有言在先:
如想更好理解和加深印象,请务必自行基于原生 LiveData 编写 demo 和复原灾难现场。
(最简单办法即是 修改 UnPeekLiveData 开源项目的示例代码:在 sample module SharedViewModel(PageMessenger.java) 中,将 Result 改为 LiveData、将 MutableResult 改为 MutableLiveData,然后照着下面例子跑一遍)
教程可明确告诉你哪有坑,但 凡事唯有从 0 到 1 完整踩过坑,才会真的有体会。
👆 👆 👆 划重点
假设 此处我们有 列表、详情、编辑 三页面,返回栈中已到达三级页面 “编辑页”,编辑页作用是编辑内容并保存和返回二级页面。
为使 编辑结果在详情页呈现,故于保存同时,我们会从编辑页通过 SharedViewModel 中 MutableLiveData 给详情页发消息,通知其刷新界面。
此时用户回到详情页,觉得内容 ok,便进一步回退到一级页面,即列表页,
那么接下来,用户在列表页中选择 另一个 item 点击,并再次跳到详情页时,就会因为观察 “已实例化过、且带有旧数据” 的 MutableLiveData,而收到它 “不符合预期” 推送。
这种 “粘性设计” 的存在意义 上文已述,很显然,它于当下这种场景会带来致命灾难。