一文搞懂 LiveData 粘性事件 和 数据倒灌

news2024/11/19 13:37:29

文章目录

  • 前言
  • LiveData使用
  • 分析粘性事件
  • 分析数据倒灌
  • 总结
一、前言

在使用LiveData的时候,会有疑问,LiveData 是怎么做到先发送再注册,依然能够接收到事件的。还有就是会碰到切换屏幕,导致重复的操作,也就是所谓的数据倒灌。这篇文章将一一揭晓。

二、LiveData使用

我们写一个CounterViewModel 继承 ViewModel,然后声明一个_counter变量。
通过点击事件触发incrementCounter() 给_counter.value 赋值,每次都加一。
这里就是通过LiveData 的 setValue 发送。

class CounterViewModel : ViewModel() {
    private val _counter = MutableLiveData<Int>()
    val counter: LiveData<Int>
        get() = _counter

    init {
        _counter.value = 0
    }
    
    fun incrementCounter() {
        val count = _counter.value ?: 0
        _counter.value = count + 1
    }
}

接着我们在Activity中的进行注册监听,我们通过ViewModelProvider 获取 viewModel 对象,
然后通过observe 进行注册监听事件。实际是通过onChanged
进行监听setValue传过来的值。
viewModel.counter.observe(this, object: Observer {
override fun onChanged(t: Int?) {
Log.d(“ssz”, “值:$t”)
}
})
下面是lambda 简化写法,实际还是通过onChanged 进行数据的接收的

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    binding = DataBindingUtil.setContentView(this, R.layout.activity_test_mvvm)
    viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
    binding.viewModel = viewModel
    binding.lifecycleOwner = this
    viewModel.counter.observe(this, {
          t -> Log.d("ssz", "值:$t")
      }
    )
    Log.d("ssz", "onCreate")
  }

总的来说,LiveData 通过setValue 发送,然后通过 Observer 里面的onChanged 进行接收。

三、分析粘性事件

通过上面的例子,我们确实能接收到数据了。那么什么是粘性事件呢,就是我们先发送再注册,这个时候依然能接收到消息,那么这就是粘性事件。就好比是我们熟悉的EventBus中的粘性事件。
那么LiveData 真的有粘性事件吗,我们打个日志就知道了,还是上面的例子,只是加上这个日志。

    init {
        _counter.value = 0
        Log.d("ssz", "初始化counter值")
    }

这是上面代码执行后的日志:
在这里插入图片描述
可看到我们是先执行了_counter.value 的初始化,也就是数据的发送,然后才接着执行oncreate中observe中的监听的。说明了LiveData 具有粘性事件。

然后这个时候,你如果正常点击,那么这个时候,就是先注册再发送了。这个就是回到正常先注册再发送了。

那么LiveData 是怎么做到粘性事件的呢。我们看下源码:

下面这是数据发送的源码:

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    }

这是注册监听observe 的关键源码:

   @SuppressWarnings("WeakerAccess") /* synthetic access */
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue()); //这是关键
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

dispatchingValue 会调用 下面的 considerNotify,也是粘性事件的真正原因

  private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) { //关键1
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData); //关键2
    }

mLastVersion 的默认初始值是-1,mVersion 的默认初始值也是-1,当我们先执行发送的时候,进行了自增,mVersion 就变成了0,当我们执行observe 进行监听的时候,observer.mLastVersion >= mVersion 这个条件就不成立了,因为此时mLastVersion 是-1,小于 mVersion 了。因为这时 mVersion 是0。

那么就不会return,就会继续执行接下来的代码
observer.mObserver.onChanged((T) mData);
看到了没有,这个onChanged 就是我们observe 里头的 onChanged。这就把数据传给了监听了。

这就是为什么,我们先发送也能接收到数据的原因了,也就是LiveData 所谓的粘性事件了。

这个时候你可能有个疑问,那正常是怎么样的呢?
正常先注册,后发送。

这里是注册的关键源码:

 @SuppressWarnings("WeakerAccess") /* synthetic access */
    void dispatchingValue(@Nullable ObserverWrapper initiator) {
        if (mDispatchingValue) {
            mDispatchInvalidated = true;
            return;
        }
        mDispatchingValue = true;
        do {
            mDispatchInvalidated = false;
            if (initiator != null) {
                considerNotify(initiator);
                initiator = null;
            } else {
                for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
                        mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
                    considerNotify(iterator.next().getValue());  //关键2
                    if (mDispatchInvalidated) {
                        break;
                    }
                }
            }
        } while (mDispatchInvalidated);
        mDispatchingValue = false;
    }

这是发送源码:

    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null); //关键1
    }

这里我们发现setValue 会去调用上面的dispatchingValue 就是会去调用注册的onChanged。一步步看。
我们能看到关键2,这里就是执行我们上面分析的considerNotify了。因为我们是先注册,再发送的,注册的 observer.mLastVersion 还是默认值-1,而发送还是老样子 mVersion 会自增,变成0。和上面分析的一样,observer.mLastVersion 小于 mVersion。这样就会执行后面的observer.mObserver.onChanged((T) mData),也就是我们注册时候的onChanged,我们就能在onChanged 接收数据了。

小结:上面我们分析了先注册后发送,还有先发送后注册的两种情况。

四、分析数据倒灌

上面能通过LiveData进行数据的发送和接收,但是当你进行横竖屏的切换,你会发现日志跟着打印的,你并没有点击发送数据呀,为什么会这样呢,这是因为当你横竖屏切换的时候,Activity进行了重新的创建,onCreate重新走了一遍。

你可能想,我这里还是走得LiveData的注册啊,但是这个时候,源码里头重新创建了个监听事件,重新new 了一个新的观察者,(相当于是上面我们提到的先发送,后注册)也就是粘性事件起了作用。

    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
        assertMainThread("observe");
        if (owner.getLifecycle().getCurrentState() == DESTROYED) {
            // ignore
            return;
        }
        LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); //这里是关键
        ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
        if (existing != null && !existing.isAttachedTo(owner)) {
            throw new IllegalArgumentException("Cannot add the same observer"
                    + " with different lifecycles");
        }
        if (existing != null) {
            return;
        }
        owner.getLifecycle().addObserver(wrapper);
    }

new LifecycleBoundObserver 重新new了一个观察者,这个LifecycleBoundObserver 其实继承了ObserverWrapper 也就是我们的mLastVersion 初始化的类。

因为重新创建了新的观察者,也就是observer.mLastVersion 重新变为初始值-1,而mVersion因为前面的发送数据,可能很多次发送了,已经大于等于0了。所以,这个时候一定会执行observer.mObserver.onChanged((T) mData)。就我们上面分析的粘性事件。这个时候,你就会收到最后一次发送的数据。

那么怎么解决呢?

1、保证Activity进行了不重新创建,也就是oncreate 不重复执行。
2、通过使用SingleLiveEvent。

第一种
很简单,直接在清单中我们配置下,比如android:configChanges=“orientation|screenSize” 保证旋转屏幕不会重新创建Activity。当然还有系统文字变化,根据自己的需要进行配置。目的保证不会重新创建Activity就行了。

第二种,
我们这里重点讲下SingleLiveEvent:

open class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val pending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner, {
            if (pending.compareAndSet(true, false)) {//这里是关键
                observer.onChanged(it)
            }
        })
    }

    @MainThread
    override fun setValue(value: T?) {
        pending.set(true)
        super.setValue(value)
    }

    @MainThread
    fun call() {
        value = null
    }
}

使用方法:

private val _snackbarText = SingleLiveEvent<String>()
val snackbarText: LiveData<String> = _snackbarText

fun onSnackbarClick() {
    _snackbarText.value = "Snackbar Clicked"
}    
   viewModel.snackbarText.observe(this, { text ->
      // 显示 Snackbar
      val rootView: View = findViewById(android.R.id.content)
      Snackbar.make(rootView, text, Snackbar.LENGTH_SHORT).show()
      Log.d("ssz","点击了" + text)
    })

我们新建一个SingleLiveEvent 继承MutableLiveData,然后通过重写observe,在里头进行判断,一旦执行过就不再执行,除非下一次的发送。这样就保证,不会数据倒灌的发生。

源码地址:https://github.com/shenshizhong/LiveDataDemo

总结

1 、简单介绍LiveData 的使用
2 、分析了粘性事件是怎么产生的,
3、 分别对先注册后发送,和先发送后注册进行了分析。
3 、分析了数据倒灌怎么回事,以及怎么处理

如果对你有一点点帮助,那是值得高兴的事情。:)
我的csdn:http://blog.csdn.net/shenshizhong
我的简书:http://www.jianshu.com/u/345daf0211ad

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

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

相关文章

postgresql的windows

1. 资源下载&#xff1a; https://www.postgresql.org/download/windows/ 2. 安装 双击&#xff0c;指定D盘目录&#xff0c;接下来默认安装&#xff0c;一直到出现下面的最后一步。一定要去除勾选复选框。 在最后&#xff0c;点击FINISH。 3. 初始化 4. 检查和修改配置 1&am…

MaxQuant的安装和_使用_(linux)

MaxQuant 是一款定量蛋白质组学软件包&#xff0c;支持多种标记定量和无标定量的质谱数据。 1. 安装 MaxQuant下载方式&#xff1a; 通过官网 下载&#xff0c;包括andromeda&#xff08;搜索引擎&#xff09;viewer&#xff08;检查原始数据、鉴定和定量结果&#xff09;。通…

使用c语言与EASYX实现弹球小游戏

eg1:小球碰到窗体的四个墙实现反弹效果 #include <stdio.h> #include <easyx.h> #include <iostream> #include <math.h> #include <conio.h> #define PI 3.14int main() {initgraph(800, 600);setorigin(400, 300);setaspectratio(1, -1);setb…

MWeb Pro for Mac:博客生成编辑器,助力你的创作之旅

在当今数字化时代&#xff0c;博客已经成为了许多人记录生活、分享知识和表达观点的重要渠道。而要打造一个专业、美观且易于管理的博客&#xff0c;选择一款强大的博客生成编辑器至关重要。今天&#xff0c;我向大家推荐一款备受好评的Mac软件——MWeb Pro。 MWeb Pro是一款专…

从龙湖智创生活入选金钥匙联盟,透视物业服务力竞争风向

假设你是业主&#xff0c;物业“服务”和“管理”&#xff0c;哪个名词看起来更加亲切、讨喜&#xff1f; 站在个人角度&#xff0c;“服务”更让人感受到温度。但对于一个要长期运营下去的住宅或者商企项目来说&#xff0c;整体的管理又必不可少。前者面向人&#xff0c;后者…

Zigbee 模组拉距测试

1.测试方法&#xff1a; 两个同型号Zigbee模组组网&#xff0c;一个作为协调器&#xff0c;一个作为路由器&#xff0c;协调器设备负责组网&#xff0c;其中路由器节点作为被测设备&#xff0c;入网网成功后一分钟开始一轮测试&#xff0c;连续发送100包数据&#xff0c;每包数…

nginx: [emerg] bind() to 0.0.0.0:18888 failed (98: Unknown error)问题解决办法

周末断网&#xff0c;今天来了之后&#xff0c;nginx出现这个问题&#xff0c;本站基本搜索的都是端口被占用问题&#xff0c;我试着杀掉所有占用端口的进程&#xff0c;解决办法 1.killall -9 nginx 2.然后启动(./nginx)nginx&#xff08;PS&#xff1a;不要./nginx -s relo…

echarts 饼图标注 字体修改

option {grid: {// 四周留白区域设置top: 20,right: 20,left: 20,bottom: 20,containLabel: true,},tooltip: {trigger: item,formatter: {b} : {c} ({d}%) // 展示百分比},series: [{type: pie,radius: [40%, 60%],avoidLabelOverlap: false,startAngle: 0, //起始角度data: …

Ajax学习笔记第4天

做决定之前仔细考虑&#xff0c;一旦作了决定就要勇往直前、坚持到底&#xff01; 【1 模仿百度招聘】 整个流程展示&#xff1a; 1.文件目录 2.页面效果展示及代码 data中的page1数据展示 2.1 主页 index.html:index里面代码部分解释 underscore.js :模板页面的相关代码 &…

香橙派OrangePi Zero开发板的WiFi连接

文章目录 调试串口连接连接WIFI设置开机自动连接自定义设置固定IP地址远程SSH连接 调试串口连接 1、准备一个 3.3v 的USB转TTL的模块&#xff0c;将开发板连接到电脑上 注意&#xff1a;引脚连接 a. USB 转 TTL 模块的 GND 接到开发板的 GND 上b. USB 转 TTL 模块的 RX 接到开…

专业课改革,难度陡然提高,专业课122总分390+南京理工大学818南理工818上岸经验分享

今年专业课相对较难&#xff0c;分数122&#xff0c;基本达到预期。南理工818是信号和数电两门课&#xff0c;各站一半。复试时间数电可能要更多一点&#xff0c;也比信号难拿分。今年专业课难度很大&#xff0c;基本超过不少985学校&#xff0c;大家要重视。 有条件的同学建议…

建筑施工二维码预制件管理系统

随着科学经济技术的发展&#xff0c;二维码标识设备越来越受人们的喜爱&#xff0c;不仅仅是在人们日常购物时需要使用到&#xff0c;在建筑用预制构件中使用也非常普遍&#xff0c;在这里二维码相当于预制构件的身份证&#xff0c;替代了传统标识牌。 1、信息管理。通过凡尔码…

倾斜摄影三维模型的顶层合并构建重要性分析

倾斜摄影三维模型的顶层合并构建重要性分析 倾斜摄影超大场景的三维模型的顶层合并对于构建精确、完整且真实的三维模型具有重要的意义和应用价值。本文将从几个方面对其重要性进行浅析。 一、模型完整性与连贯性 倾斜摄影超大场景的三维模型的顶层合并可以将多个倾斜摄影数据…

Tesseract-OCR的安装与环境变量配置

网盘链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;gtp1 中文包网盘链接&#xff1a;小蓝枣的资源仓库&#xff0c;提取码&#xff1a;8v8u 安装步骤&#xff1a;解压后直接“NEXT”&#xff0c;“NEXT”注意选择自己的安装路径。 安装后查看是否安装成功&#…

libcurl库的网页爬虫程序

示例代码&#xff1a; #include <curl/curl.h> #include <iostream> ​ int main() {CURL *curl;CURLcode res; ​curl_global_init(CURL_GLOBAL_DEFAULT); ​curl curl_easy_init();if(curl) {curl_easy_setopt(curl, CURLOPT_URL, "/");curl_easy_se…

成本预算管理系统

成本预算管理系统 功能介绍&#xff1a; 一 基本信息&#xff1a; 1、产品设置&#xff1a;产品的长、宽、高及面积计算公式的设置。 2、板材设置&#xff1a;板材类别、厚度、尺寸的设置 3、系统名称&#xff1a;风管系统的类别设置 4、公司信息&#xff1a;本公司的信息…

如何在Android设备上检查应用程序使用情况,包括使用时间

你可能不知道自己花了多少时间在手机上。很可能你一天中有一半的时间都在盯着手机屏幕。如果你怀疑这一事实,你会很快核实的。在这篇文章中,我们将向你介绍如何在Android设备上检查应用程序的使用情况。 如何在Android上检查应用程序电池使用情况 你使用时间最长的应用程序…

为什么会被【禅道】工具的公司提出QQ群的反思…………

周末备份Gitlab的代码库&#xff0c;把Gitlab更新到了最新的16.5。顺带看了禅道官网出了最新版本18.8。但是禅道的升级更新并不顺利…………。 先说一下为什么用禅道这个工具&#xff1a; 再使用禅道这个工具前&#xff0c;使用过的工具有QC(Quality Center)、jira&#xff0…

【c++|opencv】二、灰度变换和空间滤波---3.均值滤波

every blog every motto: You can do more than you think. https://blog.csdn.net/weixin_39190382?typeblog 0. 前言 均值滤波 1. 均值滤波 #include <iostream> #include <opencv2/opencv.hpp> #include"Salt.h"using namespace cv; using names…

Jetpack Compose | State状态管理及界面刷新

我们知道Jetpack Compose&#xff08;以下简称Compose&#xff09;中的 UI 可组合项是通过Composable 声明的函数来描述的&#xff0c;如&#xff1a; Composable fun Greeting() {Text(text "init",color Color.Red,modifier Modifier.fillMaxWidth()) }上面的代…