788fa4f55dd7980fab8b3eeec4c6058f
重学安卓:LiveData 数据倒灌 背景缘由全貌 独家解析

温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。

截至目前,专栏已对 体系化文章 做了 810 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。

·

注:本文以

《重学安卓:为你还原一个真实的 Jetpack Lifecycle》

《重学安卓:LiveData 鲜为人知的 身世背景 和 独特使命》

《重学安卓:有了 Jetpack ViewModel 真的可以为所欲为》

《重学安卓:是让人耳目一新的 Jetpack MVVM 精讲》 以及

《GitHub:Jetpack MVVM Best Practice》 作为前置知识,假设小伙伴已完整查阅并吸收了上述内容。

前言

很高兴见到你!

上周有位在字节工作的小伙伴私下感叹:“文章相见恨晚,恨晚呐”。

据该小伙伴描述,他早在 2017 年就开始在项目中尝试 AAC(Android Architecture Components),原以为好日子即将开始,没想到被 LiveData 的 “数据倒灌” 问题折腾得怀疑人生。🙉

是网上关于这方面的资料太少吗?不是的,与之无关。多数开发者自学一门新技术 都是首选官方文档,然而这一次的问题的根源就在于,官方文档 亲自推荐通过 SharedViewModel + LiveData 的组合来解决页面通信的问题,却在相当一段长的时间里 都没有亲自验证过并解决 该方案在场景下存在的致命缺陷

—— 架构组件的存在,本是为了规避不可预期的错误,没想到却因为这个 徒添了不可预期的错误。

姗姗来迟的笼统描述

与此类现象相关的解决方案,最早是在 2018 年中旬才在网上陆续刊登。

比如国外的:

LiveData with SnackBar, Navigation and other events

以及国内美团的:

Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus

但无论哪一种,都没有就此现象的背景做过 “因地制宜” 的概括,而只是笼统地用 “粘性事件” 来表达,也因此多数新上手 Jetpack 架构组件的小伙伴 根本无从理解这个 “粘性事件” 到底意味着什么,更不曾料想到 当下正困扰着自己的,就是这个

因此,本文的目标即是 提供对 “数据倒灌” 现象的 独家概括、缘由分析,以及趋近完美的解决方案

看完后,如你对 “数据倒灌” 这个概念有了印象、未来在遇到类似状况时能想起、并重新翻出和回溯这篇文章,那我的愿望也就达到了。😉

文章目录一览

  • 前言
  • 姗姗来迟的笼统描述
  • “数据倒灌” 的由来
  • 为什么会有数据倒灌
    • Note:2020.07.17 加餐:
    • 图文解析数据倒灌发生的场景
  • 为何非要通过 ViewModel + LiveData 来处理页面通信?
  • 为什么通过全局 ViewModel 而不是 静态单例?
  • 为什么不用 LiveDataBus?
  • 最新的 Result API 如何?
  • Event 事件包装器、反射方式、SingleLiveEvent 分别存在什么问题?
  • UnPeekLiveData 是如何解决这些问题的?
  • GitHub & JCenter 依赖
  • 综上
  • Note 2020.07.21 加餐:
    • UnPeekLiveData 增加一层 ProtectedUnPeekLiveData 的设计缘由解析
  • Note 2020.08.01 加餐:
    • UnPeekLiveData 屏蔽 observeForever 的缘由
  • Note 2020.10.15 加餐:
    • 升级到 UnPeekLiveData v4.0 享用 “更快更稳” 的防倒灌机制

“数据倒灌” 的由来

事实上,“数据倒灌” 一词,是我为了方便理解和记忆 “页面在 ‘二进宫’ 时收到旧数据推送” 的情况,而在 2019 年 自创并在网上传播的 对此类现象的概括

(再次声明 “数据倒灌” 一词 并非来自 官方文档 的概述。而且令人遗憾的是,官方文档 至今都没有就该致命问题做出警告。)

为什么会有数据倒灌

前言中已经提到,新上手的小伙伴们依赖官方文档,而官方文档给出的 “页面间通信” 场景的解决方案 存在致命缺陷 ——

一方面,用于通信的 LiveData 是被托管在 Activity / Application 级作用域的 SharedViewModel 中,于是 LiveData 的生存期长于任何一个 Fragment(假设通信双方是 Fragment):当二级 Fragment 出栈时,LiveData 实例仍存在

另一方面,LiveData 本身是被设计为粘性事件的,也即,一旦 LiveData 中持有数据,那么在观察者订阅该 LiveData 时,会被推送最后一次数据。

这样的设定本身也符合 LiveData 的常用场景 —— 比如 在页面重建时,自动推送最后一次数据,而不必重新去向后台请求

只不过,这个特性在 “页面间通信” 场景下,就是致命的灾难 ——

新上手的小伙伴完全意料不到,还会有这样 “预期外” 的状况在等着自己,于是成就了《Jetpack MVVM 高频提问和解答》篇排位 TOP 2 高频提问。

不知道我这样说,有没把话说明白?——

我举一个存在 “数据倒灌” 隐患的例子:

假设 此处我们有 列表、详情、编辑 三个页面,返回栈中已经到达了三级页面 “编辑页”,编辑页的作用是编辑内容 并保存和返回到二级页面。

为了让 编辑的结果在详情页中体现,所以在保存的同时,我们会从编辑页通过 SharedViewModel 中的 MutableLiveData 去给详情页发消息,通知其刷新界面。

此时用户回到详情页,觉得内容 ok,便进一步回退到一级页面,即列表页,

那么接下来,用户在列表页中 选择一个 item 点击,并再次跳到详情页时,就会因为注册 “已实例化过、并携带有旧数据” 的 MutableLiveData,而收到它的 “不符合预期” 的推送

这种 “粘性设计” 的存在意义 刚刚上文已经提到了,很显然,它在当下这种场景下会带来致命的灾难。

为何非要通过 ViewModel + LiveData 来处理页面通信?

关于 页面通信的场景,我们能拼凑出的背景主要有以下几块:

  • 事件的观察者是页面,但页面可能非激活状态、甚至处于被销毁状态,那么此时在消息回调中接触页面成员时,存在生命周期安全的隐患
  • 事件的观察者必须是页面,对页面的消息推送,不能让其他地方 能够监听和收到 不可预期的推送
  • 事件的发送者必须来自唯一可信源,最好是在断点调试时,能方便地直接从内存中找到对象、从而追溯到事件源
top Created with Sketch.