背景
LiveData 是效仿响应式编程 BehaviorSubject 的设计,由于
1.Jetpack 架构示例通常只包含 “表现层” 和 “数据层” 两层,缺乏在 “领域层” 分发数据的工具,
2.LiveData Observer 的设计缺乏边界感,
容易让开发者误当做 “一次性事件分发组件” 来使用,造成订阅时 "自动回推脏数据";
容易让开发者误将同一控件实例放在多个 Observer 回调中 造成恢复状态时 “数据不一致” 等问题(具体可参见《MVI 存在意义》篇 关于 “响应式编程漏洞” 的描述)
3.DataBinding ObservableField 组件的 Observer 能限定为 "与控件一对一绑定",更适合承担表现层 BehaviorSubject 工作,
4.LiveData 具备生命周期安全等优势,
因此决定将 LiveData 往领域层 PublishSubject 方向改造,去除其 “自动推送最后一次状态” 的能力,使其专职生命周期安全的数据分发,
框架现状
Note 2023.4.25:
由于 LiveData 存在的初衷并非是专业的 “一次性事件分发组件”,改造过的 UnPeekLiveData 也只适用于 “低频次数据分发(例如每秒推送 1 次)” 场景,
因而若想满足 “高频次事件分发” 需求(例如每秒推送 5 次以上),请改用或参考专职 “领域层” 数据分发的 MVI-Dispatcher 组件,该组件内部通过消息队列设计,确保不漏掉每一次推送。
Note 2021.8.20:
腾讯音乐小伙伴贡献过 v5 版重构代码,用于月活过亿 “生产环境” 痛点治理。
最新版基于 2021 年 8 月小伙伴 RebornWolfman 贡献 v7 版重构代码,稳定运行至今。
为契合消息分发语义,我们于最新版加入 MutableResult/Result 类命名。
前言
大家好,我是《Jetpack MVVM Best Practice》作者 KunMinX。
今天提到 “数据倒灌” 一词,缘于我为方便理解和记忆 “再入二级页面时收到旧数据推送” 情况,而于 2019 年 自创并在网上传播的关于此类现象概括。
它主要发生于:SharedViewModel + LiveData 组合实现页面通信场景。
背景
由于本文目标是介绍官方 Demo 现有解决方案缺陷,及经过 2 年迭代趋于完美解决方案,
所以我们假设在座诸位对最基本 “背景缘由” 有一定了解,知道:
为何 LiveData 默认被设计为粘性事件
为何 官方文档 推荐使用 SharedViewModel + LiveData(文档没明说,事实上包含三个关键背景缘由)
乃至为何存在 “数据倒灌” 现象
及为何 “页面通信” 场景下,不用静态单例或 EventBus、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)
public class TestFragment extends Fragment {
protected void onViewCreate(){
viewModel.getXXXResult().observe(this, xxx ->{
renderUI(...);
})
viewModel.requestXXX();
}
}
public class SharedViewModel extends ViewModel {
private final MutableResult<XXX> xxxResult = new MutableResult<>();
public Result<XXX> getXXXResult(){
return xxxResult;
}
public void requestXXX(){
//业务逻辑 ...
...
xxxResult.setValue(...);
}
}
且 UnPeekLiveData 提供构造器模式,后续可通过构造器组装适合自己业务场景 UnPeekLiveData。
MutableResult<Moment> test =
new MutableResult.Builder<Moment>()
.setAllowNullValue(false)
.create();
Maven 依赖
implementation 'com.kunminx.arch:unpeek-livedata:7.8.0'
温馨提示:
1.上述 implementation 命名,我们已从
archi
改为arch
,请注意修改,2.鉴于 Jcenter 关闭,我们已将仓库迁移至 Maven Central,请自行在根目录 build.gradle 添加
mavenCentral()
。
Thanks
PS:感谢近期 hegaojian、Angki、Flynn、Joker_Wan、小暑知秋、大棋、空白、qh、lvrenzhao、半节树人 等小伙伴积极试用和反馈,使潜在问题被及时发现和纳入考虑。
GitHub & Maven 依赖
详见:GitHub:KunMinX/UnPeekLiveData
License
本文以 CC 署名-非商业性使用-禁止演绎 4.0 国际协议 发行。
Copyright © 2019-present KunMinX
文中提到的 对 “数据倒灌” 一词及其现象的概括、对 Event 事件包装器、反射方式、SingleLiveEvent 各自存在的缺陷的理解,以及对 UnPeekLiveData 的 “延迟自动清理消息” 的设计,均属于本人独立原创的成果,本人对此享有所有权和最终解释权。
当您借鉴或引用本文的引言、思路、结论进行二次创作,或全文转载时,须注明链接出处,否则我们保留追责的权利。