Giter Site home page Giter Site logo

kunminx / unpeek-livedata Goto Github PK

View Code? Open in Web Editor NEW
1.1K 13.0 119.0 523 KB

LiveData 数据倒灌:别问,问就是不可预期 - Perfect alternative to SingleLiveEvent, supporting multiple observers.

Java 100.00%
jetpack jetpack-android livedata livedata-viewmodel androidx lifecycle android eventbus livedatabus singlelivedata

unpeek-livedata's Introduction

背景

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、半节树人 等小伙伴积极试用和反馈,使潜在问题被及时发现和纳入考虑。

 

谁在使用

根据小伙伴们私下反馈和调查问卷,我们了解到

包括 “腾讯音乐、BMW、TCL” 在内诸多知名厂商软件,都参考过我们开源的 Jetpack MVVM Scaffold 架构模式,及正在使用我们维护的 UnPeek-LiveData 等框架。

“问卷调查” 我们长期保持对外开放,如有意可自行登记,以吸引更多小伙伴参与使用和反馈,集众人之所长,让架构组件得以不断演化和升级。

https://wj.qq.com/s2/8362688/124a/

集团 / 公司 产品
腾讯音乐 QQ 音乐
ezen Egshig音乐
TCL 内置文件管理器
左医科技 诊室听译机器人
BMW Speech
上海互教信息有限公司 知心慧学教师
美术宝 弹唱宝
网安

 

历史版本

版本 更新日期
UnPeekLiveData v7.8 2022.9.06
UnPeekLiveData v7.6 2022.6.13
UnPeekLiveData v7.5 2022.6.12
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 v3 2020.07.10
UnPeekLiveData v2 2020.05.09
UnPeekLiveData v1 2019.07.15

 

最新更新动态

UnPeekLiveData v7.8 特点

clear 方法改名 clean,以免开发者和集合 clear 方法弄混。

UnPeekLiveData v7.6 特点

事件 Event 倾向于 "发送" 语境,事件通常来自 UI 层,例如通过 Button click 发起某事件。

而 mutable 系框架倾向于 "接收事件、处理业务逻辑" 后的末流消息分发。

为此,基于 "单一职责原则",最终我们将 UnPeekLiveData 更名为 Result,示意其纯粹 "消息分发" 用途,

通过语义,让团队成员在 Activity/Fragment 中使用 result.setValue 时感觉别扭,促使其仅于 "唯一可信源" 内部业务逻辑末端使用。

 

UnPeekLiveData v7.5 特点

为方便后来者学习参考,往期版本 UnPeekLiveData 类皆存留库中,

现将过往类从库中移至 Wiki,感兴趣可自行查阅。

 

UnPeekLiveData v7.2 特点

根据小伙伴 @RebornWolfman分享,通过更简便方式修复 v7.0 潜在 removeObserver 内存泄漏问题。

 

UnPeekLiveData v7.1 特点

修复 removeObserver 潜在的内存泄漏。

 

UnPeekLiveData v7.0 特点

感谢小伙伴 @RebornWolfman分享

相较上一版,V7 版源码在 "代理类/包装类" 自行维护一个版本号,在 UnPeekLiveData 中维护一个当前版本号,且分别在 setValue 和 Observe 时机改变和对齐版本号,如此使得无需额外管理 Observer Map,从而进一步规避内存管理问题,

是继 V6 版源码以来,最简源码设计。

具体可参见 UnPeekLiveData 最新源码及注释说明。

 

UnPeekLiveData v6.1 特点

根据小伙伴 @liweiGe启发,我们将 state 演变为 ObserverProxy 字段来管理,从而使 Map 合二为一,代码逻辑进一步简化。

 

UnPeekLiveData v6.0 特点

V6 版源码翻译和完善自小伙伴 @wl0073921分享

相比 V5 版改进之处在于,引入 Observer 代理类设计,这使页面旋屏重建时,无需通过反射方式跟踪和复用基类 Map 中 Observer,转而通过 removeObserver 方式自动移除和在页面重建后重建新 Observer,

因而复杂度由原先分散于基类数据结构,到集中在 proxy 对象这一处,进一步方便源码逻辑阅读和后续修改。

具体可参见 UnPeekLiveData 最新源码及注释说明。

 

UnPeekLiveData v5.0 特点

感谢 “腾讯音乐” 小伙伴 @zhangjianlaoda 贡献的重构优化代码。

该版本保留 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.

unpeek-livedata's People

Contributors

kunminx avatar rebornwolfman avatar zhangjianlaoda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

unpeek-livedata's Issues

7.1内存泄漏

当使用 removeObservers(@nonnull final LifecycleOwner owner) 的时候,回调过来已经是ObserverWrapper,又封装了一层,导致没有删除掉。应该是加一个判断,判断回调当前类是否已经是ObserverWrapper的类型,不是才需要包装删除,那时候没有测试想到。
@OverRide
public void removeObserver(@nonnull @NotNull Observer<? super T> observer) {
if (observer.getClass().isAssignableFrom(ObserverWrapper.class)) {
super.removeObserver(observer);
} else {
super.removeObserver(createObserverWrapper(observer, START_VERSION, false));
}
}

写了个简版的防止倒灌,不知道行不行

public class EventMutableLiveData extends LiveData {

private final AtomicInteger currentVersion = new AtomicInteger(-1);

/**
 * 这个标志主要是是否忽略最新值, observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) 才会生效
 * 比如从页面1 来到页面2 页面2 再回到页面1 页面1默认是会收到页面2 最新的值,不想收到这个值可以设置为true
 */
private boolean isIgnoreLastObserve;

private final HashMap<Observer<? super T>, ObserverProxy> observerMap = new HashMap<>();

public EventMutableLiveData(boolean isIgnoreLastObserve) {
    super();
    this.isIgnoreLastObserve = isIgnoreLastObserve;
}

public EventMutableLiveData() {
    super();
}

@Override
public void observe(@NonNull @NotNull LifecycleOwner owner, @NonNull @NotNull Observer<? super T> observer) {
    if (!observerMap.containsKey(observer)) {
        observerMap.put(observer, new ObserverProxy(observer, currentVersion.get(), false));
    } else {
        Objects.requireNonNull(observerMap.get(observer)).changeVersion(currentVersion.get());
    }
    super.observe(owner, Objects.requireNonNull(observerMap.get(observer)));
}

@Override
public void observeForever(@NonNull @NotNull Observer<? super T> observer) {
    if (!observerMap.containsKey(observer)) {
        observerMap.put(observer, new ObserverProxy(observer, currentVersion.get(), true));
    }
    super.observeForever(Objects.requireNonNull(observerMap.get(observer)));
}

@Override
public void setValue(T value) {
    currentVersion.getAndIncrement();
    super.setValue(value);
}

@Override
public void postValue(T value) {
    currentVersion.getAndIncrement();
    super.postValue(value);
}

class ObserverProxy implements Observer<T> {

    private final Observer<? super T> observer;

    private int mVersion = -1;

    private final boolean isForever;

    public ObserverProxy(@NotNull Observer<? super T> observer, int mVersion, boolean isForever) {
        this.observer = observer;
        this.mVersion = mVersion;
        this.isForever = isForever;
    }

    @Override
    public synchronized void onChanged(T t) {
        if (currentVersion.get() > mVersion) {
            observer.onChanged(t);
            if (isIgnoreLastObserve && !isForever) {
                Set<Map.Entry<Observer<? super T>, ObserverProxy>> set = observerMap.entrySet();
                for (Map.Entry<Observer<? super T>, ObserverProxy> item : set) {
                    item.getValue().changeVersion(currentVersion.get());
                }
            }
        }
    }

    private void changeVersion(int newVersion) {
        this.mVersion = newVersion;
    }
}

@Override
public void removeObserver(@NonNull @NotNull Observer<? super T> observer) {
    super.removeObserver(observer);
    observerMap.remove(observer);
}

public void removeAllObserver() {
    Set<Map.Entry<Observer<? super T>, ObserverProxy>> set = observerMap.entrySet();
    for (Map.Entry<Observer<? super T>, ObserverProxy> item : set) {
        super.removeObserver(item.getValue());
    }
    observerMap.clear();
}

}

【 提问须知 】

如有 bug,请另外 new 一个 issue ⚠️⚠️⚠️

本项目开 issue 规范:

  1. 有任何 bug 都欢迎及时开 issue,我看到后予以处理。
  2. 如有使用上的疑问,请先认真阅读 Readme 和源码 sample,在没有找到答案后,另外开 issue。
  3. 如开 issue 是为了发表个人见解,请务必 客观、具体、严谨;严禁草率、乱入、带节奏:

务必注明观点所对应的场景,并附上完整可复现的代码,

不然缺乏一致的前提依据来有效交流!

任何缺乏实证依据和因果逻辑的泛泛而谈,都可能对其他使用者造成困扰。

在发表个人见解前,请先确保自己认真阅读过源码。这是对自己、对作者、对其他读者最起码的尊重。

ViewModel has no zero argument constructor

使用 getApplicationScopeViewModel(Class modelClass) 方法初始化 ViewModel 时,如果传入的是一个 AndroidViewModel,会报
java.lang.Class<com.kunminx.puremusic.domain.message.SharedViewModel> has no zero argument constructor

想请教下 AndroidViewModel 怎么使用 Application context 初始化?

其实根本没有所谓的数据倒灌, 我这里想做一个解释

描述问题

我是一个 MVVM 架构和响应式编程深度使用的一个开发者. Github 上刷到你的这个仓库. 我还是想对你们所说的数据倒灌进行解释说明一下.

你所说的数据倒灌其实根本不是一个问题或者 bug.
LiveData 设计就是如此. 接受最近一个信号. 对应流的 Behavior 模式.
我们有知名度一点的流的实现有 RxJava 和 Kotlin 的 Flow. 在他们的实现中, 分别对应 BehaviorSubject 和 StateFlow
他们的图示如下, 你们可以看到, 在不同的时间点发生订阅, 你总是能收到最近的一个信号. 除非一开始就没发射过信号. 而 LiveData 正是类似于此种模式. 所以你们说它的数据倒灌, 其实根本不是问题, 人家设计本是如此.

那你们说的数据倒灌根本原因是因为什么呢?
其实是因为你们监听了 Behavior 模式的流或者 LiveData 去做了相应的操作.
比如你监听一个 LiveData 去做了网络请求
当你界面第一次进入, LiveData 中产生了一个信号, 你收到之后做了一次请求, 后来由于系统配置更改引起界面重建, 但是 ViewModel 还是原先那个, 所以在界面重建后你去监听 LiveData, 就会立马收到一个信号, 导致你又做了一次请求.
这里说明的场景, 是由于错误使用了 LiveData 引起的. 如果你要监听一个信号做一定的行为, 这类通常是需要监听 Publish 模式的流. 而 LiveData 设计之初就是 Behavior 模式, Publish 模式的行为示意图如下, 你只能收到你订阅点之后的信号.

总结

  • Behavior 模式可选的方案
    • RxJava 的 BehaviorSubject
    • Kotlin Flow 的 StateFlow
    • LiveData
  • Publish 模式可选的方案
    • RxJava 的 PublishSubject
    • Kotlin Flow 的 SharedFlow
    • 我们自定义的 Listener 等

综上所述, 在响应式编程中, 由于你接受的信号源有不同的模式实现. 所以在平常的业务需求中, 我们也要合理的进行选择.

比如我们用于显示界面的场景, Behavior 模式是最适合不过了, 这也是为什么 LiveData 出现的原因. 本就为了显示页面的数据去的. 在界面重建也能重新进行显示.

再比如我们用于执行某些行为的场景, 比如你收到一个信号进行一次网络请求、数据库操作、跳转等等. 这些其实都是需要使用 Publish 模式的.

希望我在这里的长篇大论, 能很好的解释你们出现的所谓的数据倒灌的问题. 在项目中能合理的选择对应的实现去解决问题. 并且对一个响应式的数据源进行监听的时候, 需要先知道它的实现模式是 Behavior 还是 Publish, 以便于你做出判断, 可以用作哪些场景的使用

还是会发生数据倒灌

image

VoicePtaView 是自定义 ConstraintLayout,里面包含多个Fragment。
当VoicePtaView 多次被 removeVIew 并 addVIew时, 就会发生数据倒灌,收到多次回调。

Fragment中
val viewModel by activityViewModels()

初始化
val mTTSLiveData: MutableResult<BaseResponse<List>> =
MutableResult.Builder<BaseResponse<List>>().setAllowNullValue(true).create()

/// 订阅
viewModel.mTTSCloneListDataLiveData.observe(viewLifecycleOwner){}

UnPeekLiveData能否读写分离

只能在ViewModel中写数据。

private val _phoneInput: MutableLiveData<String>
val phoneInput: LiveData<String>
        get() = _phoneInput

外部只能拿到phoneInput,只能observe。
希望UnPeekLiveData也能做相应操作。

observers默认值为什么是true?

observe时,默认设置为true,这个设计是为什么呢?
考虑Fragment和Activity交互时,Fragment后observe一条数据,此时如果首次observe默认值为true,会导致第一次的数据无法收到。数据倒灌为什么要把第一次observe的数据也屏蔽

倒灌问题的理解

倒灌问题的产生原因:1.重新注册监听,2.livedata有数据。如果livedata用来发送事件场景时,应该是只关心添加监听之后的数据,添加之前的数据是不关心的。
倒灌问题的解决方案的思考:1.数据的管理者可能不关心数据的消费者是要什么样的数据(粘性、还是非粘性),2.livedata 的行为结果整个应用内应该是一致。因此我认为扩展方法比修改原方法可能更合适。
不成熟的解决方案:

/**
 * LiveData扩展事件监听,去掉粘性,去掉空数据
 * 订阅之前的数据不会回调。只会收到非空的数据。只在前台是收到回调,自动反注册
 */
fun <T> LiveData<T>.observeEvent(owner : LifecycleOwner,observer : Observer<T>){
    val ob = ObserverWrapper(this,observer)
    this.observe(owner,ob)
}

/**
 * 包装一下,用于判断是不是添加监听之后的数据,之前的数据不回调,空数据不回调
 * 事件不能为空
 */
internal class ObserverWrapper<T>(liveData: LiveData<T>,private val mObserver: Observer<in T>) : Observer<T> {
    private var skipValue = liveData.value !=null
    override fun onChanged(t: T) {
        if (!skipValue && t != null) {
            mObserver.onChanged(t)
        }
        skipValue = false
    }

    override fun equals(o: Any?): Boolean {
        if (this === o) {
            return true
        }
        if (o == null || javaClass != o.javaClass) {
            return false
        }
        val that = o as ObserverWrapper<*>
        return mObserver == that.mObserver
    }

    override fun hashCode(): Int {
        return Objects.hash(mObserver)
    }
}

ProtectedUnPeekLiveDataV5疑问

你好,我在阅读源码试着理解版本代码演进的逻辑,但是我发现 V5版本还是存在HashMap恒久存在,注册的 Observer 越多,占用的内存越大问题
image
image
image

发现在退出页面调用removeObserver 方法时,入参 observer 进行 Hash 运算得到的 observeKey 与注册 Observer 时得到的observeKey 不一样

这段代码写的有问题

public UnPeekLiveData create() {
UnPeekLiveData liveData = new UnPeekLiveData<>();
liveData.isAllowNullValue = this.isAllowNullValue;
return liveData;
}

UnPeekLiveData 没有这个isAllowNullValue 变量。

针对使用两个ConcurrentHashMap的问题优化,可参考

observerStateMap 这个map 感觉没有存在的必要,可以更加精简

private final ConcurrentHashMap<Observer<? super T>, ChangeablePair<Observer<? super T>, Boolean>> observerProxyMap = new ConcurrentHashMap();

这是我的想法,一个map就可以搞定
image

我觉得数据上会更好

另外针对这一行代码
image
改造成如下:
image

其实这样就可以了,不需要包一层

open class ProtectedUnPeekLiveData<T : Any?> : LiveData<T?>() {

    private val pending = AtomicBoolean(false)
    protected var isAllowNullValue = false

    override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
        super.observe(owner) { t ->
            if (pending.compareAndSet(true, false)
                && (t != null || isAllowNullValue)
            ) {
                observer.onChanged(t)
            }
        }
    }

    override fun observeForever(observer: Observer<in T?>) {
        super.observeForever { t ->
            if (pending.compareAndSet(true, false)
                && (t != null || isAllowNullValue)
            ) {
                observer.onChanged(t)
            }
        }
    }

    fun observeSticky(owner: LifecycleOwner, observer: Observer<T?>) {
        super.observe(owner, observer)
    }

    fun observeStickyForever(observer: Observer<T?>) {
        super.observeForever(observer)
    }

    override fun setValue(value: T?) {
        if (value != null || isAllowNullValue) {
            pending.set(true)
            super.setValue(value)
        }
    }

    fun clear() {
        super.setValue(null)
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.