前言
大家好,我是《Jetpack MVVM Best Practice》作者 KunMinX。
今天提到的 “数据倒灌” 一词,缘于我为了方便理解和记忆 “页面在 ‘二进宫’ 时收到旧数据推送” 的情况,而在 2019 年 自创并在网上传播的 关于此类现象的概括。
它主要发生在:通过 SharedViewModel + LiveData 的组合 来解决页面通信的场景。
背景
由于本文的目标主要是来介绍 官方 Demo 现有解决方案的缺陷,以及经过 2 年迭代的趋于完美的解决方案,
所以我们假设在座的诸位 对最基本的背景缘由有一定的了解,知道:
为什么 LiveData 默认被设计为粘性事件
为什么 官方文档 推荐使用 SharedViewModel + LiveData(文档没明说,但事实上包含三个关键的背景缘由)
乃至为什么存在 “数据倒灌” 的现象
以及为什么在 “页面通信” 的场景下,不用静态单例、不用 LiveDataBus
如果对于这些前置知识也尚不了解,可结合个人的兴趣前往《LiveData 数据倒灌 背景缘由全貌 独家解析》查阅,此处不再累述。
现有解决方案及各自缺陷
在《Jetpack MVVM 精讲》中我们分别提到了 Event 事件包装器、反射方式、SingleLiveEvent 这三种方式来解决 “数据倒灌” 的问题。它们分别来自上文我们提到的外网、美团的文章,和官方最新 demo。
但正如我们在《Jetpack MVVM 精讲》介绍的,它们分别存在如下问题:
Event 事件包装器:
对于多观察者的情况,只允许第一个观察者消费,这不符合现实需求;
而且手写 Event 事件包装器,在 Java 中存在 null 安全的一致性问题。
反射干预 Version 的方式:
存在延迟,无法用于对实时性有要求的场景;
并且数据会随着 SharedViewModel 长久滞留在内存中得不到释放。
官方最新 demo 中的 SingleLiveEvent:
是对 Event 事件包装器 一致性问题的改进,但未解决多观察者消费的问题;
而且额外引入了消息未能从内存中释放的问题。
UnPeek-LiveData 特点
1.一条消息能被多个观察者消费(since v1.0)
2.消息被所有观察者消费完毕后才开始阻止倒灌(since v4.0)
3.可以通过 clear 方法手动将消息从内存中移除(since v4.0)
4.让非入侵设计成为可能,遵循开闭原则(since v3.0)
5.基于 "访问权限控制" 支持 "读写分离",遵循唯一可信源的消息分发理念(since v2.0,详见 ProtectedUnPeekLiveData)
并且 UnPeekLiveData 提供构造器模式,后续可通过构造器组装适合自己业务场景的 UnPeekLiveData。
UnPeekLiveData<Moment> test =
new UnPeekLiveData.Builder<Moment>()
.setAllowNullValue(false)
.create();
Maven 依赖
implementation 'com.kunminx.arch:unpeek-livedata:7.2.0-beta1'
温馨提示:
1.上述 implementation 的命名,我们已从
archi
改为arch
,请注意修改,2.鉴于 Jcenter 的关闭,我们已将仓库迁移至 Maven Central,请自行在根目录 build.gradle 添加
mavenCentral()
。
Thanks
PS:非常感谢近期 hegaojian、Angki、Flynn、Joker_Wan、小暑知秋、大棋、空白、qh、lvrenzhao、半节树人 等小伙伴积极的试用和反馈,使得未被觉察的问题 被及时发现和纳入考虑。
谁在使用
通过小伙伴的私下反馈和调查问卷我们了解到
包括 “腾讯音乐、BMW、TCL” 在内的诸多知名厂商的软件,都参考过我们开源的 Jetpack MVVM Scaffold 架构模式,以及正在使用我们维护的 UnPeek-LiveData 等框架。
问卷调查我们长期保持对外开放,如有意可自行登记,以便吸引更多小伙伴 参与到对这些架构组件的 使用、反馈,集众人之所长,让架构组件得以不断演化和升级。
集团 / 公司 | 产品 |
---|---|
腾讯音乐 | 即将更新,敬请期待 |
TCL | 内置应用,暂时保密 |
左医科技 | 诊室听译机器人 |
BMW | Speech |
上海互教信息有限公司 | 知心慧学教师 |
美术宝 | 弹唱宝 |
网安 |
历史版本
版本 | 更新日期 |
---|---|
UnPeekLiveData v7.2 | 2021.8.20 |
UnPeekLiveData v7.1 | 2021.8.16 |
UnPeekLiveData v7 | 2021.8.10 |
UnPeekLiveData v6.1 | 2021.7.15 |
UnPeekLiveData v6 | 2021.6.17 |
UnPeekLiveData v5 | 2021.4.21 |
UnPeekLiveData v4.4 | 2021.1.27 |
UnPeekLiveData v4.2 | 2020.12.15 |
UnPeekLiveData v4 | 2020.10.16 |
最新更新动态
UnPeekLiveData v7.2 特点
感谢小伙伴 @RebornWolfman 在 issue 的分享,通过更简便的方式来修复 v7.0 潜在的 removeObserver 内存泄漏问题。
UnPeekLiveData v7.1 特点
修复 removeObserver 潜在的内存泄漏。
UnPeekLiveData v7.0 特点
感谢小伙伴 @RebornWolfman 在 issue 中的分享,
相较于上一版,我们在 V7 版源码中,通过在 "代理类/包装类" 中自行维护一个版本号,在 UnPeekLiveData 中维护一个当前版本号,分别来在 setValue 和 Observe 的时机来改变和对齐版本号,如此使得无需另外管理一个 Observer map,从而进一步规避了内存管理的问题,
同时这也是继 V6 版源码以来,最简的源码设计,方便阅读理解和后续修改。
具体可参见 UnPeekLiveData 最新源码及注释的说明。
UnPeekLiveData v6.1 特点
相较于上一版,我们在 V6.1 版源码中,将 state 演变为 ObserverProxy 的字段来管理,从而将 Map 合二为一,让代码逻辑进一步简化。
UnPeekLiveData v6.0 特点
感谢小伙伴 @wl0073921 对 UnPeekLiveData 源码的演化做出的贡献,
V6 版源码翻译和完善自小伙伴 wl0073921 在 issue 中的分享,
V6 版源码相比于 V5 版的改进之处在于,引入 Observer 代理类的设计,这使得在旋屏重建时,无需通过反射方式跟踪和复用基类 Map 中的 Observer,转而通过 removeObserver 的方式来自动移除和在页面重建后重建新的 Observer,
因而复杂度由原先的分散于基类数据结构,到集中在 proxy 对象这一处,进一步方便了源码逻辑的阅读和后续的修改。
具体可参见 UnPeekLiveData 最新源码及注释的说明。
UnPeekLiveData v5.0 特点
感谢就职于 “腾讯音乐部门” 的小伙伴 @zhangjianlaoda 应邀对 UnPeekLiveData 做的优化和升级。
该版本保留了 UnPeekLiveData v4 的下述几大特点,并在适当时机基于反射等机制,来彻底解决 UnPeekLiveData v4 下 Observers 无法释放、重复创建,以及 foreverObserver、removeObserver 被禁用等问题,将 UnPeekLiveData 的内存性能再往上提升了一个阶梯。
同时,该版本使 Observe 等方法的方法名和形参列表与官方 API 保持一致,尽可能减少新上手小伙伴的学习成本。
具体可参见 UnPeekLiveData 最新源码及注释的说明。
UnPeekLiveData v4.0 特点
我们在 UnPeekLiveData v3.0 的基础上,参考了小伙伴 Flywith24 - WrapperLiveData 遍历 ViewModelStore 的思路,以此提升 “防止倒灌时机” 的精准度。
注:出于在现有 AndroidX 源码的背景下实现 "防倒灌机制" 的需要,v4.0 对 Observe 方法的使用做了微调,改为分别针对 Activity/Fragment 提供 ObserveInActivity 和 ObserveInFragment 方法,具体缘由详见源码注释的说明。
UnPeekLiveData v3.0
Update since 2020.7.10
通过 独创的 “延时自动清理消息” 的设计,来满足:
1.消息被分发给多个观察者时,不会因第一个观察者消费了而直接被置空
2.时限到了,消息便不再会被倒灌
3.时限到了,消息自动从内存中清理释放
4.使非入侵的设计成为可能,并最终结合官方 SingleLiveEvent 的设计实现了 遵循开闭原则的非入侵重写。
UnPeekLiveData v2.0
Update since 2020.5
1.结合 Event 包装类的使用,对 LiveData 类进行入侵性修改。
2.提供 ProtectedUnPeekLiveData,基于访问权限控制实现 "读写分离":支持只从 "唯一可信源"(例如 ViewModel)内部发送、而 Activity/Fragment 只允许 Observe。
UnPeekLiveData v1.0
Update since 2019
1.针对 “页面在 ‘二进宫’ 时收到旧数据推送” 的情况 创建 “数据倒灌” 的定义,并在网上交流和传播。
2.参考美团 LiveDataBus 的设计,透过反射的方式拦截并修改 Last Version 来防止倒灌。
License
本文以 CC 署名-非商业性使用-禁止演绎 4.0 国际协议 发行。
Copyright © 2019-present KunMinX
文中提到的 对 “数据倒灌” 一词及其现象的概括、对 Event 事件包装器、反射方式、SingleLiveEvent 各自存在的缺陷的理解,以及对 UnPeekLiveData 的 “延迟自动清理消息” 的设计,均属于本人独立原创的成果,本人对此享有最终解释权。
文中 "最新更新动态" 处提到的新版源码设计,以及对新版源码思路的理解和解析,属于参与过新版设计讨论的有效贡献者及本人的共同成果,我们对此享有所有权和最终解释权。
任何个人或组织在引用上述内容时,须注明原作者和链接出处。未经授权不得用于洗稿、广告包装、卖课等商业用途。
Copyright 2019-present KunMinX
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.