温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。
截至目前,专栏已对 体系化文章 做了 1070 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。
·
注:本文以
《重学安卓:为你还原一个真实的 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 年 自创并在网上传播的 对此类现象的概括。
遗憾的是,官方文档 至今 (截至 2020.7.15) 都没有意识到 并就该致命问题做出警告。
为什么会有数据倒灌
前言中已经提到,新上手的小伙伴们依赖官方文档,而官方文档给出的 “页面间通信” 场景的解决方案 存在致命缺陷 ——
一方面,用于通信的 LiveData 是被托管在 Activity / Application 级作用域的 SharedViewModel 中,于是 LiveData 的生存期长于任何一个 Fragment(假设通信双方是 Fragment):当二级 Fragment 出栈时,LiveData 实例仍存在。
另一方面,LiveData 本身是被设计为粘性事件的,也即,一旦 LiveData 中持有数据,那么在观察者订阅该 LiveData 时,会被推送最后一次数据。
这样的设定本身也符合 LiveData 的常用场景 —— 比如 在页面重建时,自动推送最后一次数据,而不必重新去向后台请求。
只不过,这个特性在 “页面间通信” 场景下,就是致命的灾难 ——
新上手的小伙伴完全意料不到,还会有这样 “预期外” 的状况在等着自己,于是成就了读者群 “高频提问和解答” TOP 2。
不知道我这样说,有没把话说明白?——
我举一个存在 “数据倒灌” 隐患的例子:
假设 此处我们有 列表、详情、编辑 三个页面,返回栈中已经到达了三级页面 “编辑页”,编辑页的作用是编辑内容 并保存和返回到二级页面。
为了让 编辑的结果在详情页中体现,所以在保存的同时,我们会从编辑页通过 SharedViewModel 中的 MutableLiveData 去给详情页发消息,通知其刷新界面。

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

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

这种 “粘性设计” 的存在意义 刚刚上文已经提到了,很显然,它在当下这种场景下会带来致命的灾难。
为何非要通过 ViewModel + LiveData 来处理页面通信?
关于 页面通信的场景,我们能拼凑出的背景主要有以下几块:
- 事件的观察者是页面,但页面可能非激活状态、甚至处于被销毁状态,那么此时在消息回调中接触页面成员时,存在生命周期安全的隐患。
- 事件的观察者必须是页面,对页面的消息推送,不能让其他地方 能够监听和收到 不可预期的推送。
- 事件的发送者必须来自唯一可信源,如此在 “多页面状态同步” 等场景下,能够因 “数据总是来自唯一可信源的统一决策和分发”,而 确保了状态同步的一致性和可靠性。
而 ViewModel + LiveData 的组合,无疑是这种现状下 超低成本 解决上述 3 大问题的最优解,因而 问题也就进一步地转化为 解决 LiveData 的 “数据倒灌” 问题。
为什么通过全局 ViewModel 而不是 静态单例?
这个问题是过去 1 年读者群里问到最多的问题,被排在 “高频提问和解答” 的 TOP 1。
原因很简单,因为:
1.ViewModel 是可以声明为页面的私有成员来使用,如此可以 将 “页面通信” 的消息限制在 “仅在视图控制器之间传播”,避免污染到外部。
温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。
截至目前,专栏已对 体系化文章 做了 1070 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。
·
注:本文以
《重学安卓:为你还原一个真实的 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 年 自创并在网上传播的 对此类现象的概括。
遗憾的是,官方文档 至今 (截至 2020.7.15) 都没有意识到 并就该致命问题做出警告。
为什么会有数据倒灌
前言中已经提到,新上手的小伙伴们依赖官方文档,而官方文档给出的 “页面间通信” 场景的解决方案 存在致命缺陷 ——
一方面,用于通信的 LiveData 是被托管在 Activity / Application 级作用域的 SharedViewModel 中,于是 LiveData 的生存期长于任何一个 Fragment(假设通信双方是 Fragment):当二级 Fragment 出栈时,LiveData 实例仍存在。
另一方面,LiveData 本身是被设计为粘性事件的,也即,一旦 LiveData 中持有数据,那么在观察者订阅该 LiveData 时,会被推送最后一次数据。
这样的设定本身也符合 LiveData 的常用场景 —— 比如 在页面重建时,自动推送最后一次数据,而不必重新去向后台请求。
只不过,这个特性在 “页面间通信” 场景下,就是致命的灾难 ——
新上手的小伙伴完全意料不到,还会有这样 “预期外” 的状况在等着自己,于是成就了读者群 “高频提问和解答” TOP 2。
不知道我这样说,有没把话说明白?——
我举一个存在 “数据倒灌” 隐患的例子:
假设 此处我们有 列表、详情、编辑 三个页面,返回栈中已经到达了三级页面 “编辑页”,编辑页的作用是编辑内容 并保存和返回到二级页面。
为了让 编辑的结果在详情页中体现,所以在保存的同时,我们会从编辑页通过 SharedViewModel 中的 MutableLiveData 去给详情页发消息,通知其刷新界面。

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

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

这种 “粘性设计” 的存在意义 刚刚上文已经提到了,很显然,它在当下这种场景下会带来致命的灾难。
为何非要通过 ViewModel + LiveData 来处理页面通信?
关于 页面通信的场景,我们能拼凑出的背景主要有以下几块:
- 事件的观察者是页面,但页面可能非激活状态、甚至处于被销毁状态,那么此时在消息回调中接触页面成员时,存在生命周期安全的隐患。
- 事件的观察者必须是页面,对页面的消息推送,不能让其他地方 能够监听和收到 不可预期的推送。
- 事件的发送者必须来自唯一可信源,如此在 “多页面状态同步” 等场景下,能够因 “数据总是来自唯一可信源的统一决策和分发”,而 确保了状态同步的一致性和可靠性。
而 ViewModel + LiveData 的组合,无疑是这种现状下 超低成本 解决上述 3 大问题的最优解,因而 问题也就进一步地转化为 解决 LiveData 的 “数据倒灌” 问题。
为什么通过全局 ViewModel 而不是 静态单例?
这个问题是过去 1 年读者群里问到最多的问题,被排在 “高频提问和解答” 的 TOP 1。
原因很简单,因为:
1.ViewModel 是可以声明为页面的私有成员来使用,如此可以 将 “页面通信” 的消息限制在 “仅在视图控制器之间传播”,避免污染到外部。
温馨提示:如果这是第一次接触《重学安卓》,可借助 这份在 GitBook 上维护的 “导读” 来快速了解《重学安卓》专栏、获取它的目录、试读内容,以及了解它的最新动态 和 发展状况。
截至目前,专栏已对 体系化文章 做了 1070 余次修订,数十位群友告诉我 受专栏的启发 他们也开启了写作之路。群里不定期会有小伙伴讨论适配问题、分享原创的开源库 和 提供内推机会,订阅后可随时进群交流。
·
注:本文以
《重学安卓:为你还原一个真实的 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 年 自创并在网上传播的 对此类现象的概括。
遗憾的是,官方文档 至今 (截至 2020.7.15) 都没有意识到 并就该致命问题做出警告。
为什么会有数据倒灌
前言中已经提到,新上手的小伙伴们依赖官方文档,而官方文档给出的 “页面间通信” 场景的解决方案 存在致命缺陷 ——
一方面,用于通信的 LiveData 是被托管在 Activity / Application 级作用域的 SharedViewModel 中,于是 LiveData 的生存期长于任何一个 Fragment(假设通信双方是 Fragment):当二级 Fragment 出栈时,LiveData 实例仍存在。
另一方面,LiveData 本身是被设计为粘性事件的,也即,一旦 LiveData 中持有数据,那么在观察者订阅该 LiveData 时,会被推送最后一次数据。
这样的设定本身也符合 LiveData 的常用场景 —— 比如 在页面重建时,自动推送最后一次数据,而不必重新去向后台请求。
只不过,这个特性在 “页面间通信” 场景下,就是致命的灾难 ——
新上手的小伙伴完全意料不到,还会有这样 “预期外” 的状况在等着自己,于是成就了读者群 “高频提问和解答” TOP 2。
不知道我这样说,有没把话说明白?——
我举一个存在 “数据倒灌” 隐患的例子:
假设 此处我们有 列表、详情、编辑 三个页面,返回栈中已经到达了三级页面 “编辑页”,编辑页的作用是编辑内容 并保存和返回到二级页面。
为了让 编辑的结果在详情页中体现,所以在保存的同时,我们会从编辑页通过 SharedViewModel 中的 MutableLiveData 去给详情页发消息,通知其刷新界面。

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

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

这种 “粘性设计” 的存在意义 刚刚上文已经提到了,很显然,它在当下这种场景下会带来致命的灾难。
为何非要通过 ViewModel + LiveData 来处理页面通信?
关于 页面通信的场景,我们能拼凑出的背景主要有以下几块:
- 事件的观察者是页面,但页面可能非激活状态、甚至处于被销毁状态,那么此时在消息回调中接触页面成员时,存在生命周期安全的隐患。
- 事件的观察者必须是页面,对页面的消息推送,不能让其他地方 能够监听和收到 不可预期的推送。
- 事件的发送者必须来自唯一可信源,如此在 “多页面状态同步” 等场景下,能够因 “数据总是来自唯一可信源的统一决策和分发”,而 确保了状态同步的一致性和可靠性。
而 ViewModel + LiveData 的组合,无疑是这种现状下 超低成本 解决上述 3 大问题的最优解,因而 问题也就进一步地转化为 解决 LiveData 的 “数据倒灌” 问题。
为什么通过全局 ViewModel 而不是 静态单例?
这个问题是过去 1 年读者群里问到最多的问题,被排在 “高频提问和解答” 的 TOP 1。
原因很简单,因为:
1.ViewModel 是可以声明为页面的私有成员来使用,如此可以 将 “页面通信” 的消息限制在 “仅在视图控制器之间传播”,避免污染到外部。