LiveData源码分析,粘性事件,数据倒灌

news2025/1/21 4:46:48

在这里插入图片描述

最近面试天天被虐,有个问题问的很频繁,就是 LiveData 的数据倒灌问题怎么解决。

我不知道有多少人连数据倒灌是什么都没听过的,更不要说什么解决方案啦。

我按照我的理解描述一下数据倒灌:就是设置了 LiveData 的数据之后,再观察 LiveData,这时候拿到的数据是观察之前设置的数据,用比较难懂的说法就是之前设置的数据倒灌过来了。越说越乱了,其实就是一个粘性事件,不管你什么时候观察,都可以拿到最后设置的数据。

举个例子:我把接口返回的错误信息保存到一个 LiveData 中,在当前 Fragment 的 onViewCreated 中绑定,观察到错误信息后弹出了提示框,我关闭了提示框,跳到了另一个 Fragment,然后再返回刚才那个 Fragment,奇迹发生了,它又弹窗了!!!

先分析源码:

以下的代码都是部分关键代码,一些不想看的看不懂的代码我直接删掉了,,,

创建LiveData对象:

// 这样是正常写法吧
val liveData = MutableLiveData<String>()

// 类:LiveData
// 这个是LiveData的构造方法
public LiveData() {
	// mData就是LiveData保存的最后一次更新的数据
    // private volatile Object mData;
    // static final Object NOT_SET = new Object();
    mData = NOT_SET;

	// 这个是LiveData的数据版本号,每一次更新数据版本号都会+1
    // private int mVersion;
    // static final int START_VERSION = -1;
    mVersion = START_VERSION;
}

观察LiveData对象:

// 是这样观察吧
liveData.observe(lifeCycleOwner) { 
    println(it) 
}

// 执行:liveData.observe(lifeCycleOwner) { println(it) }
// 类:LiveData
public void observe(LifecycleOwner owner, Observer<? super T> observer) {
    // 把生命周期和观察者绑定起来,构造方法在下面
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    
    // 所有观察者保存到mObservers里面
    // private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = new SafeIterableMap<>();
    // 如果mObservers已经存在wrapper,则返回
    // 如果mObservers不存在wrapper的话,则put进去,返回null
    // 所以最终mObservers里面保存着所有的观察者
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    
    // 我们这里只考虑第一次添加观察者的情况,直接绑定生命周期
    // 至此,观察者已经添加完毕
    owner.getLifecycle().addObserver(wrapper);
}

// 执行:new LifecycleBoundObserver(owner, observer);
// 类:LifecycleBoundObserver
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
    @NonNull
    final LifecycleOwner mOwner;

    LifecycleBoundObserver(LifecycleOwner owner, Observer<? super T> observer) {
        super(observer);
        mOwner = owner;
    }
}

// 执行:super(observer);
// 类:ObserverWrapper
private abstract class ObserverWrapper {
    final Observer<? super T> mObserver;

    ObserverWrapper(Observer<? super T> observer) {
        mObserver = observer;
    }
}

进入生命周期:

ObserverWrapper里面有一个mActive变量,如果没有进入生命周期,mActive默认是false的。
从人类角度思考的话,就是某个观察者,如果它绑定的生命周期没有进入到STARTED 状态的话,是不会激活的,没激活的话就不会观察到任何东西。
生命周期变化会回调onStateChanged 方法:

// 类:LifecycleBoundObserver(继承LifecycleEventObserver)
public void onStateChanged(@NonNull LifecycleOwner source,
        @NonNull Lifecycle.Event event) {
    // DESTROYED状态,移出观察者
    // 从这里可以看出LiveData不需要手动解除观察者,都是自动的
    Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
    if (currentState == DESTROYED) {
        removeObserver(mObserver);
        return;
    }

    // 这里加了个prevState,确保在状态有变化之后才处理
    Lifecycle.State prevState = null;
    while (prevState != currentState) {
        prevState = currentState;
        // shouldBeActive(),至少是STARTED状态以上才会返回true
        activeStateChanged(shouldBeActive());
        currentState = mOwner.getLifecycle().getCurrentState();
    }
}

// 执行:activeStateChanged(shouldBeActive());
// 类:ObserverWrapper
void activeStateChanged(boolean newActive) {
    // 我们只考虑正常情况,newActive=true
    // mActive默认为false
    if (newActive == mActive) {
        return;
    }
    mActive = newActive;
    if (mActive) {
        // 分发,具体看setValue()部分,传入了具体的观察者
        // 这个代码是在ObserverWrapper里面的,这个this就是观察者,
        // 意思就是向这个观察者分发数据
        dispatchingValue(this);
    }
}

LiveData.setValue():

// kotlin:setValue()
liveData.value = "hello"

// 类:LiveData
protected void setValue(T value) {
    // 每次setValue版本号都会+1,postValue最终也是setValue
    mVersion++;
    // 保存最后更新的数据
    mData = value;
    // 给观察者们分发数据
    // 没有传入具体的观察者,而是传入了null,表示给所有观察者分发
    dispatchingValue(null);
}

// 执行:dispatchingValue(null);
// 类:LiveData
void dispatchingValue(@Nullable ObserverWrapper initiator) {
    if (mDispatchingValue) {
        mDispatchInvalidated = true;
        return;
    }
    mDispatchingValue = true;
    do {
        mDispatchInvalidated = false;
        if (initiator != null) {
            // 如果传入了具体的观察者,则直接调用considerNotify
            // 绑定观察者的时候会马上分发
            considerNotify(initiator);
            initiator = null;
        } else {
            // 没有传入具体的观察者,则遍历mObservers,拿到每一个观察者执行considerNotify
            for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                    mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                considerNotify(iterator.next().getValue());
                if (mDispatchInvalidated) {
                    break;
                }
            }
        }
    } while (mDispatchInvalidated);
    mDispatchingValue = false;
}

// 执行:considerNotify(iterator.next().getValue());
// 类:LiveData
private void considerNotify(ObserverWrapper observer) {
    if (!observer.mActive) {
        return;
    }

    // 我们只考虑分发的情况
    // mLastVersion默认是-1
	// 这里很重要,
	// 观察者每次收到数据后都会把自己的版本号设置成LiveData的版本号
	// 所以当观察者的版本号大于等于LiveData的版本号,
	// 那就说明这个观察者已经处理过这个版本的数据了
    if (observer.mLastVersion >= mVersion) {
        return;
    }

	// 每个观察者也会保存一份自己的版本号
    observer.mLastVersion = mVersion;

    // 至此回调用户定义的观察者,收工
    observer.mObserver.onChanged((T) mData);
}

LiveData.postValue():

// 使用:
liveData.postValue("hello")

// 类:LiveData
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        // mPendingData默认是NOT_SET
        // static final Object NOT_SET = new Object();
        // volatile Object mPendingData = NOT_SET;
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    
    // 通过hanlder提交到主线程执行(所有需要主线程跑的代码全部都是通过handler提交的)
    // 很多JectPack的库都有用到这个ArchTaskExecutor
    // 我们自己的代码也可以直接用它,也可以给它设置我们自己的线程池
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        // 这里回到setValue()的情况
        setValue((T) newValue);
    }
};

粘性事件和数据倒灌

从上面分析可以看到LiveData里面保存的上一次分发的数据mData,这是一个Object对象,并且在分发完毕后不会置空,所以后来的观察者也能观察到这个对象。

解决思路:

不要解决!!!

LiveData 本来就是这么设计的,是用来保存数据的,不是用来分发事件的。

解决思路参考:

1、反射。我看很多博客都说可以用反射,在 observe 的时候反射拿到 LiveData 的版本号,再反射赋值给 Observer 的版本号,但是,从理论上分析我就觉得不可能。首先 LifecycleBoundObserver 是在 observe 方法中生成的,通过反射根本拿不到这个对象。但是可以从 mObservers 中拿到,然而是在 super.observe(owner, observer) 之后才能拿到,这时候已经绑定生命周期并且触发分发了,拿到 Observer 还有什么意义。

2、https://github.com/KunMinX/UnPeek-LiveData 这个库看着可行。大概原理就是,自己定义一个 MyLiveData 和 MyObserver,然后自己维护一份 MyLiveData 和 MyObserver 的版本号,在创建 MyObserver 的时候把 MyObserver 的版本号设置成 MyLiveData 的版本号,在 MyObserver 的 onChanged 方法中判断 MyLiveData 的版本号大于等于 MyObserver 的版本号才执行。和前面反射的原理其实是差不多的,只是不反射了,而是自己维护一套版本号。

3、其他的都不用考虑了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1268898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

“rhdf5filters.so’ not found when install ‘glmGamPoi‘ package

在R中安装glmGamPoi包的时候&#xff0c;出现了如下报错&#xff1a; install.packages(glmGamPoi) 尝试方案一&#xff1a; sudo apt install pkg-config libhdf5-dev安装lighdf5-dev&#xff0c;并将安装路径链接至usr/lib/文件。 locate rhdf5filters.so sudo ln -s /hom…

武汉建筑类初级职称助理工程师电子版证书申报

武汉建筑类初级职称助理工程师电子版证书申报 目前大家较为关注的是湖北省的助理工程师/初级职称评审出来之后是否可以网上查询。市面上还有一些地级市的区人社职改办出纸质版证书&#xff0c;职称证书、红头文件、评审表齐全&#xff0c;但是查询方式还是老一套的查询方式&am…

三勾商城新功能发布-多包裹订单

在不同场景下&#xff0c;商家可能需一笔订单需要分成多个包裹、分批发货&#xff0c;来看看怎么操作吧。 前端截图 后台截图 三勾小程序商城基于springbootelement-plusuniapp打造的面向开发的小程序商城&#xff0c;方便二次开发或直接使用&#xff0c;可发布到多端&#xf…

职场人最好的姿势是仰卧起坐

曾经看过一个回答说“职场人最好的姿势是仰卧起坐”。 卷累的就躺&#xff0c;休息好了再继续卷&#xff0c;卷是常态&#xff0c;“仰卧起坐”也好&#xff0c;“卷的姿势”也好&#xff0c;都是在反复“卷起”的过程中寻找一些舒适和平衡&#xff0c;“卷”得更持久罢了.....…

Linux 进程(一)

1 操作系统 概念&#xff1a;任何计算机系统都包含一个基本的程序集合&#xff0c;称为操作系统(OS)。笼统的理解&#xff0c;操作系统包括 内核&#xff08;进程管理&#xff0c;内存管理&#xff0c;文件管理&#xff0c;驱动管理&#xff09; 其他程序&#xff08;例…

LeetCode(41)单词规律【哈希表】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 单词规律 1.题目 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&#xff0c; pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连…

Elasticsearch 快照如何工作?

作者&#xff1a;Lutf ur Rehman Elastic 提供许多由讲师指导的面对面和虚拟现场培训以及点播培训。 我们的旗舰课程是 Elasticsearch 工程师、Kibana 数据分析和 Elastic 可观测性工程师。 所有这些课程都会获得认证。有关这些课程的详细介绍&#xff0c;请参考我之前的文章 “…

20.Oracle11g中的触发器

oracle11g中的触发器 一、触发器的概述1、什么是触发器2、触发器的类型3、触发器的组成4、触发器的作用 二、触发器的创建语法1、创建语法2、数据库启动触发器3、 用户登录触发器&#xff1a; 三、对触发器的基本操作点击此处跳转下一节&#xff1a;21.Oracle的程序包(Package)…

QNX下多窗口叠加融合方案

目的&#xff1a;QNX下EGL多窗口叠加融合方案 环境&#xff1a; 系统&#xff1a;QNX 环境&#xff1a;8155/8295问题&#xff1a; EGL有时候在同一个进程中因为引入不同的功能&#xff0c;在不同的线程中进行窗口的绘制和融合&#xff0c;QNX下的融合方案&#xff0c;实测使…

夸克大模型助力学术科研提效 四大优势提升知识正确性

当严谨的学术科研与创新的大模型技术结合在一起&#xff0c;会擦出什么样的火花&#xff1f;日前&#xff0c;夸克大模型甫一推出便以优秀的性能成为国产大模型中的“学霸”。在中国科学技术协会近期主办的“大模型应用场景研讨会”上&#xff0c;夸克大模型在快速阅读、创作润…

求臻医学胃癌关爱日:美味的高“盐”值杀手

胃癌的发病率具有广泛的地域差异&#xff0c;在东南亚国家尤为高发。韩国是胃癌发病率排名第一的国家&#xff0c;其次为日本&#xff0c;中国紧随其后&#xff0c;由于中国人口基数大&#xff0c;其绝对患胃癌人数为全球第一&#xff0c;每年有100多万新诊断患者&#xff0c;其…

nvm for windows使用与node/npm/yarn的配置

1 下载 nvm for windows download – github 下拉到Assets, 下载.exe文件 2 安装 安装到如下文件夹中 目录可以自己选, 可以换别的名字, 自己记住即可 新手建议全部看完再进行个人配置, 或者使用与博主一致的路径 D:\DevelopEnvironment\nvm3 配置nvm使用的镜像 node_mir…

wvp 视频监控平台抓包分析

抓包时机 下面的抓包时机是抓包文件最新&#xff0c;但是最有用的包 选择网卡开始抓包 如果之前已经选择网卡&#xff0c;直接开始抓包 停止抓包 重新抓包 sip播放过程分析 过滤条件 tcp.port 5060 and sip 可以看到有这些包 选择任何一个 &#xff0c;戍边右键--追踪流--…

Maven——使用Nexus创建私服

私服不是Maven的核心概念&#xff0c;它仅仅是一种衍生出来的特殊的Maven仓库。通过建立自己的私服&#xff0c;就可以降低中央仓库负荷、节省外网带宽、加速Maven构建、自己部署构件等&#xff0c;从而高效地使用Maven。 有三种专门的Maven仓库管理软件可以用来帮助大家建立…

【C++】异常处理 ① ( 异常概念引入 | 抛出异常语法 | 捕获异常语法 | 异常捕获流程 | 异常处理代码示例 )

文章目录 一、异常处理1、异常概念引入2、抛出异常语法3、捕获异常语法4、异常捕获流程 二、异常处理代码示例1、错误代码示例 - 抛出异常 / 不捕获异常2、正确代码示例 - 抛出异常 / 捕获异常3、正确代码示例 - 抛出异常 / 捕获异常不处理继续抛出异常 一、异常处理 1、异常概…

【导航控制器的基本使用 Objective-C语言】

一、导航控制器的基本使用 1.那接下来呢,我们就要讲解这个重中之重了啊,导航控制器,大家一定要注意听,那首先呢,我们先来看ppt,引导一下, 导航控制器.pptx,打开, 那接下来呢,我们就要学习这个多控制器的管理了, 里面的第一个内容,叫做导航控制器, 那今天呢,我们…

unity学习笔记08

一、预制体 在Unity中&#xff0c;预制体&#xff08;Prefab&#xff09;是一种特殊类型的游戏对象&#xff0c;它允许你创建、配置和保存一个对象&#xff0c;然后在场景中多次使用。预制体的使用使得开发者能够更加灵活和高效地设计和管理游戏对象。 1.创建预制体 可以选择…

在很多nlp数据集上超越tinybert 的新架构nlp神经网络模型

在很多nlp数据集上超越tinybert 的新架构nlp神经网络模型 网络结构图测试代码网络结构图 测试代码 import paddle import numpy as np import pandas as pd from tqdm import tqdmclass FeedFroward(paddle.nn.Layer):

专业的调查问卷平台推荐:提升数据收集与分析效率

无论是学生还是职场人士&#xff0c;想做好一份调查问卷&#xff0c;关键先要确定调查的主题&#xff0c;然后确定调查人群&#xff0c;编辑问题&#xff0c;最后能够尽可能的美化问卷调查的主题。 想要做到这几点&#xff0c;就要要求问卷调查平台: 1、能够帮助你快速制作出一…

TA-Lib学习研究笔记(一)

TA-Lib学习研究笔记&#xff08;一&#xff09; 1.介绍 TA-Lib&#xff0c;英文全称“Technical Analysis Library”,是一个用于金融量化的第三方库&#xff0c;涵盖了150多种交易软件中常用的技术分析指标&#xff0c;如RSI,KDJ,MACD, MACDEXT, MACDFIX, SAR, SAREXT, MA,SM…