【Android Jetpack】LiveData-观察数据的容器

文章目录

  • LiveData
    • LiveData与ViewModel
    • 创建LiveData对象
    • 观察LiveData中的数据
    • 更新LiveData对象
    • observeForever()
    • 源码
    • Room和LiveData配合使用
    • 继承LiveData扩展功能
    • 转换LiveData
    • 合并多个LiveData中的数据

LiveData

ViewModel的主要作用是存放页面所需要的各种数据。我们在示例代码中定义了接口,当数据发生变化时,采用接口的方式实现对页面的通知。对此前面已经做了相关说明,通过接口的方式对页面进行通知是可行的,但如果要观察的数据很多,则需要定义大量的接口,代码会显得十分冗余。为此,Jetpack提供了LiveData组件。

LiveData是一个可被观察的数据容器类。具体说来,可以将LiveData理解为一个数据的容器,它将数据包装起来,使数据成为被观察者,当该数据发生变化时,观察者能够获得通知。我们不需要自己去实现观察者模式,LiveData内部已经默认实现好了,我们只要使用就可以了。

LiveData取代了上一章中所定义的接口,帮助我们完成ViewModel与页面之间的通信。

LiveData是一个可观察的数据持有者类。与常规的可观察对象不同,LiveData具有生命周期意识,这意味着它尊重其他应用程序组件的生命周期,如活动、片段或服务。这种意识确保LiveData只更新处于活动生命周期状态的应用程序组件观察员。//翻译于官方文档

LiveData是一种持有可被观察数据的类。和其他可被观察的类不同的是,LiveData是有生命周期感知能力的,这意味着它可以在activities,fragments,或者services生命周期是活跃状态时更新这些组件。那么什么是活跃状态呢?ViewModel中的**STARTEDRESUMED就是活跃状态,只有在这两个状态下LiveData是会通知数据变化的。**

要想使用LiveData(或者这种有可被观察数据能力的类)就必须配合实现了LifecycleOwner的对象使用。在这种情况下,当对应的生命周期对象DESTROYED时,才能移除观察者。这对Activity或者Fragment来说显得尤为重要,因为他们可以在生命周期结束的时候立刻解除对数据的订阅,从而避免内存泄漏等问题。

使用LiveData的优点:

  • UI和实时数据保持一致 因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI
  • 避免内存泄漏,观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destory)时,观察者会立刻自动清理自身的数据。
  • 不会再产生由于Activity处于stop状态而引起的崩溃 例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
  • 不需要再解决生命周期带来的问题LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
  • 实时数据刷新,当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据
  • 解决Configuration Change问题,在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
  • 数据共享,如果对应的LiveData是单例的话,就能在app的组件间分享数据。

LiveData与ViewModel

ViewModel用于存放页面所需要的各种数据,不仅如此,我们还可以在其中放一些与数据相关的业务逻辑。例如,我们可以在ViewModel中进行数据的加工、获取等操作。因此,ViewModel中的数据可能会随着业务的变化而变化。对页面来说,它并不关心ViewModel中的业务逻辑,它只关心需要展示的数据是什么,并且希望在数据发生变化时,能及时得到通知并做出更新。LiveData的作用就是,在ViewModel中的数据发生变化时通知页面。因此,LiveData通常被放在ViewModel中

使用LiveData:

  • 创建一个持有某种数据类型的LiveData(通常是在ViewModel中)
  • 创建一个定义了onChange()方法的观察者。这个方法是控制LiveData中数据发生变化时,采取什么措施 (比如更新界面)。通常是在UI Controller(Activity/Fragment)中创建这个观察者。
  • 通过LiveData实例的observe()方法连接观察者和LiveDataobserve()方法需要携带一个LifecycleOwner类。这样就可以让观察者订阅LiveData中的数据,实现实时更新。

创建LiveData对象

LiveData是一个抽象类,不能直接使用。通常我们使用的是它的直接子类MutableLiveData

LiveData是一个数据的包装。具体的包装对象可以是任何数据,包括集合(比如List)。LiveData通常在ViewModel中创建,然后通过getter方法获取。

class TimerViewModel : ViewModel() {private val timer: Timer = Timer()private var currentSecond: MutableLiveData<Int> = MutableLiveData() //创建LiveData对象fun start() {timer.schedule(timerTask {var value = currentSecond.valueval plus = value?.plus(1)currentSecond.postValue(plus)}, 1000, 1000)}fun getCurrentSecond() : LiveData<Int> {return currentSecond}override fun onCleared() {super.onCleared()timer.cancel()}
}

观察LiveData中的数据

通常情况下都是在组件的onCreate()方法中开始观察数据,原因有以下两点:

  • 系统会多次调用onResume()方法
  • 确保Activity/Fragment在处于活跃状态时立刻可以展示数据。
class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)val tv = findViewById<TextView>(R.id.tv)val model = ViewModelProvider(this).get(TimerViewModel::class.java)// 得到ViewModelval liveData = model.getCurrentSecond() as MutableLiveData<Int>//获取LiveData,并调用LiveData实例的observe()方法liveData.observe(this, object : Observer<Int> {override fun onChanged(t: Int?) {tv.text = t.toString()}})// 重置liveData.postValue(0)model.start()}
}

更新LiveData对象

如果想要在UI Controller中改变LiveData中的值呢?(比如点击某个Button把性别从男设置成女)。LiveData并没有提供这样的功能,但是Architecture Component提供了MutableLiveData这样一个类,可以通过setValue(T)postValue(T)方法来修改存储在LiveData中的数据。MutableLiveDataLiveData的一个子类,从名称上也能看出这个类的作用。举个直观点的例子:

mButton.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {String anotherName = "John Doe";mModel.getCurrentName().setValue(anotherName);}
})

调用setValue()方法就可以把LiveData中的值改为John Doe。同样通过这种方法修改LiveData中的值同样会触发所有对这个数据感兴趣的类。那么setValue()postValue()有什么不同呢?区别就是setValue()只能在主线程中调用,而postValue()可以在子线程中调用。

observeForever()

LiveData还提供了一个名为observeForever()的方法,使用起来与observe()没有太大差别。区别在于,当LiveData所包装的数据发生变化时,无论页面处于什么状态,observeForever()都能收到通知。(正所谓永远观察)因此,在用完之后,一定要记得调用removeObserver()方法来停止对LiveData的观察,否则LiveData会一直处于激活状态,Activity则永远不会被系统自动回收,这就造成了内存泄漏。

源码

还有两个重要的方法,分别是observe()方法和setValue()方法:

    @MainThreadpublic void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// ignorereturn;}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;}// 将Observer与Activity的生命周期关联在一起。因此,LiveData能够感知页面的生命周期。它可以检测页面当前的状态是否为激活状态,或者页面是否被销毁。只有在页面处于激活状态(Lifecycle.State.ON_STARTED或Lifecycle.State.ON_RESUME)时,页面才能收到来自LiveData的通知,若页面被销毁(Lifecycle.State.ON_DESTROY),那么LiveData会自动清除与页面的关联,从而避免可能引发的内存泄漏问题。owner.getLifecycle().addObserver(wrapper);}
    /*** Sets the value. If there are active observers, the value will be dispatched to them.* 设置值。如果存在活动的观察者,则值将被分派给他们。* <p>* This method must be called from the main thread. If you need set a value from a background* 必须从主线程调用此方法。如果需要从后台线程设置值* thread, you can use {@link #postValue(Object)}** @param value The new value*/@MainThreadprotected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);}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(); ) {// 遍历mObservers,调用considerNotify()更新数据considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;}
// considerNotify()方法:   private void considerNotify(ObserverWrapper observer) {if (!observer.mActive) {return;}// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.//// we still first check observer.active to keep it as the entrance for events. So even if// the observer moved to an active state, if we've not received that event, we better not// notify for a more predictable notification order.//我们仍然首先检查observer.active,将其作为活动的入口。//因此,即使观察者移动到活动状态,如果我们没有收到该事件,我们最好不要通知更可预测的通知顺序。if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;//回调onChanged()方法observer.mObserver.onChanged((T) mData);}

Room和LiveData配合使用

Room可以返回LiveData的数据类型。这样对数据库中的任何改动都会被传递出去。这样修改完数据库就能获取最新的数据,减少了主动获取数据的代码。

//我还没学完就不展示了

继承LiveData扩展功能

LiveData的活跃状态包括:STARTED或者RESUMED两种状态。那么如何在活跃状态下把数据传递出去呢?下面是示例代码:

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {private val stockManager = StockManager(symbol)private val listener = { price: BigDecimal ->value = price}override fun onActive() {stockManager.requestPriceUpdates(listener)}override fun onInactive() {stockManager.removeUpdates(listener)}
}

上面有三个重要的方法:

  • onActive(): 当LiveData对象具有活动观察者时调用该方法
  • onInactive(): 当LiveData对象没有任何活动观察者时调用该方法
  • setValue(T): 更新LiveData实例的值,并通知任何活动的观察者有关更改的信息

可以像下面这样使用StockLiveData:

public class MyFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)val myPriceListener: LiveData<BigDecimal> = ...myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->// Update the UI.})}
}

上面observe()方法中的第一个参数传递的是fragment的实例,该fragment实现了LifecycleOwner接口。这样做是为了将observerLifecycle对象绑定到一起,这意味着:

  • 如果当前的Lifecycle对象不是处于活跃期,就算value值有改变也不会回调到observer
  • Lifecycle对象销毁后,observer对象也会自动移除,防止内存泄漏

实际上LiveData对象是适应生命周期也就意味着你需要在多个activities,fragmentsservices中进行共享,所以通常我们会将LiveData的示例设计成单例的:

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {private val stockManager: StockManager = StockManager(symbol)private val listener = { price: BigDecimal ->value = price}override fun onActive() {stockManager.requestPriceUpdates(listener)}override fun onInactive() {stockManager.removeUpdates(listener)}companion object {private lateinit var sInstance: StockLiveData@MainThreadfun get(symbol: String): StockLiveData {sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)return sInstance}}

这样就可以在fragment中像如下这样使用:

class MyFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->// Update the UI.})}
}    

转换LiveData

有时在LiveData分发给observers之前想要修改一下存储在LiveData中的值,或者你想根据当前的值进行修改返回另一个值。Lifecycle提供了Transformations类来通过里面的helper方法解决这种问题。

  • Transformations.map()

可以将LiveData中的数据进行改变:

val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = Transformations.map(userLiveData) {user -> "${user.name} ${user.lastName}"
}

LiveData中的User数据转换成String

  • Transformations.switchMap()
private fun getUser(id: String): LiveData<User> {...
}
val userId: LiveData<String> = ...
val user = Transformations.switchMap(userId) { id -> getUser(id) }

和上面的map()方法很像。区别在于传递给switchMap()的函数必须返回LiveData对象。
LiveData一样,Transformation也可以在观察者的整个生命周期中存在。只有在观察者处于观察LiveData状态时,Transformation才会运算Transformation是延迟运算的(calculated lazily),而生命周期感知的能力确保不会因为延迟发生任何问题。

如果在ViewModel对象的内部需要一个Lifecycle对象,那么使用Transformation是一个不错的方法。举个例子:假如有个UI组件接受输入的地址,返回对应的邮政编码。那么可以 实现一个ViewModel和这个组件绑定:

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {private fun getPostalCode(address: String): LiveData<String> {// DON'T DO THISreturn repository.getPostCode(address)}
}

有个// DON'T DO THIS(不要这么干),这是为什么?有一种情况是如果UI组件被回收后又被重新创建,那么又会触发一次repository.getPostCode(address),而不是重用上次已经获取到的查询。那么应该怎样避免这个问题呢?看一下下面的代码:

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {private val addressInput = MutableLiveData<String>()val postalCode: LiveData<String> = Transformations.switchMap(addressInput) {address -> repository.getPostCode(address) }//仅在输入变化时调用private fun setInput(address: String) {addressInput.value = address}
}

postalCode变量的修饰符是publicfinal,因为这个变量的是不会改变的。那输入不同的地址还总返回相同邮编?当然不是,postalCode这个变量存在的作用是把输入的addressInput转换成邮编,那么只有在输入变化时才会调用repository.getPostCode()方法。这就好比你用final来修饰一个数组,虽然这个变量不能再指向其他数组,但是数组里面的内容是可以被修改的。绕来绕去就一点:当输入是相同的情况下,用了switchMap()可以减少没有必要的请求。并且同样,只有在观察者处于活跃状态时才会运算并将结果通知观察者。

合并多个LiveData中的数据

MediatorLiveDataLiveData的子类,可以通过MediatorLiveData合并多个LiveData来源的数据。同样任意一个来源的LiveData数据发生变化,MediatorLiveData都会通知观察他的对象。说的有点抽象,举个栗子。比如UI接收来自本地数据库和网络数据,并更新相应的UI。可以把下面两个LiveData加入到MeidatorLiveData中:

  • 关联数据库的LiveData

  • 关联联网请求的LiveData
    相应的UI只需要关注MediatorLiveData就可以在任意数据来源更新时收到通知。

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

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

相关文章

数据结构与算法编程题28

计算二叉树结点总数 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std;typedef char ElemType; #define ERROR 0 #define OK 1 #define Maxsize 100 #define STR_SIZE 1024typedef struct BiTNode {ElemType data;BiTNode* lchild, * rchild; }B…

科研工具推荐之ReadPaper

科研工具推荐之ReadPaper 之前也用很多朋友在问英文文献如何阅读&#xff0c;一直推荐的是Adobe PDF有道翻译。 但是呢&#xff0c;最近了解到了另外一个神器 ReadPaper 自己稍微体验了一下 感觉非常nice&#xff0c;特此推荐给大家。 想体验的朋友可以通过下面的方式注册呢…

提升APP软件的用户体验方法

提升APP软件的用户体验是确保用户满意度和应用成功的关键。以下是一些方法&#xff0c;可以帮助提升APP的用户体验&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.简洁的用户界面设计&#xff1a; …

「C++」类和对象1

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;C启航 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;前言&#xff1a;初识面向对象&#x1f349;类&#x1f34c;类的定义&#x1f34c;访问限定符&#x1f95d;类的…

动态规划专题——背包问题

&#x1f9d1;‍&#x1f4bb; 文章作者&#xff1a;Iareges &#x1f517; 博客主页&#xff1a;https://blog.csdn.net/raelum ⚠️ 转载请注明出处 目录 前言一、01背包1.1 使用滚动数组优化 二、完全背包2.1 使用滚动数组优化 三、多重背包3.1 使用二进制优化 四、分组背包…

Codeforces Round 911 (Div. 2) --- D题题解

D. Small GCD Problem - D - Codeforces 题目大意&#xff1a; 给你一个数组&#xff0c;你可以在里面任选三个数ai aj ak&#xff0c;要求i j k 互不相同&#xff0c; 现定义一个函数f(a,b,c)gcd(a,b)&#xff0c;其中a 和 b为a&#xff0c;b&#xff0c;c中较小的两个。求f…

大数据平台/大数据技术与原理-实验报告--MapReduce编程

实验名称 MapReduce编程 实验性质 &#xff08;必修、选修&#xff09; 必修 实验类型&#xff08;验证、设计、创新、综合&#xff09; 综合 实验课时 2 实验日期 2023.10.30-2023.11.03 实验仪器设备以及实验软硬件要求 专业实验室&#xff08;配有centos7.5系统…

【代码随想录刷题】Day18 二叉树05

文章目录 1.【513】找树左下角的值1.1题目描述1.2 解题思路1.2.1 迭代法思路1.2.2 递归法思路 1.3 java代码实现1.3.1 迭代法java代码实现1.3.2 递归法java代码实现 2. 【112】路径总和2.1题目描述2.2 解题思路2.3 java代码实现 3.【106】从中序与后序遍历序列构造二叉树3.1题目…

Linux | Linux入门及常用基础命令介绍

关注CodingTechWork Linux Linux介绍 概述 Linux出现的时候是没有图像化界面&#xff0c;都是黑屏操作&#xff0c;靠命令来完成操作&#xff0c;如磁盘读写、网络管理等。企业级服务器的维护基本都通过跳板机ssh到对应的服务器上进行操作&#xff0c;一般无图形化界面。 远…

DevEco Studio对同一套HarmonyOS代码进行多设备端预览

鸿蒙代码有一个很大的优势 不需要其他的语法 只需要一套HarmonyOS代码 就可以在 手机 平板 电脑上运行 我们可以在DevEco Studio预览器上 点击如下图指向位置 弹出的这个窗口中 我们将右上角的开关勾选上 这样 我们调试器向下滚动 就可以看到多端预览的一个效果了

开源与闭源:数字时代大模型之辩

欢迎大家到我的博客浏览更多文章。YinKais Blog | YinKais Blog 大模型的未来&#xff1a;开源与闭源的博弈 在大模型的发展中&#xff0c;开源和闭源两种截然不同的开发模式发挥着重要的作用。开源以其技术共享的特性&#xff0c;吸引了大量人才参与&#xff0c;推动了大模型的…

Windows Server 2012R2 修复CVE-2016-2183(SSL/TLS)漏洞的办法

一、漏洞说明 Windows server 2012R2远程桌面服务SSL加密默认是开启的,且有默认的CA证书。由于SSL/ TLS自身存在漏洞缺陷,当开启远程桌面服务,使用漏洞扫描工具扫描,发现存在SSL/TSL漏洞。远程主机支持的SSL加密算法提供了中等强度的加密算法,目前,使用密钥长度大于等于5…

队列实现方式、效率分析及应用场景

文章目录 一、什么是队列二、队列特性阻塞和非阻塞有界和无界单向链表和双向链表 三、Java队列接口继承图四、Java队列常用方法五、队列实现方式与效率分析六、队列的应用场景七、Python中队列与优先级队列使用 一、什么是队列 队列是一种特殊的线性表&#xff0c;遵循先入先出…

express习惯养成小程序-计算机毕设 附源码 32209

习惯养成小程序的设计与实现 摘 要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;习惯养成小程序被用户普遍使…

WebSocket协议在java中的使用

学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。各位小伙伴&#xff0c;如果您&#xff1a; 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持&#xff0c;想组团高效学习… 想写博客但无从下手&#xff0c;急需…

Centos7上面部署redis

Centos7上面部署redis 编写这个部署redis&#xff0c;只是为了另一个文章入侵redis做准备&#xff0c;网上还有好多类似的文章&#xff0c;这个单纯的就是部署安装&#xff0c;并简单的测试使用以下 关联其他文章 [1]VMware上面安装部署centos7镜像系统【详细含镜像】 [2]血的教…

美女骑士开箱VELO Angel TT,银色天使,无痛骑行

阳光、女孩、自行车&#xff0c;脸上的笑容或明媚&#xff0c;或神秘&#xff0c;或青涩&#xff0c;在这个时候&#xff0c;世界上没有什么比骑行女孩更美的了&#xff01;      在北京&#xff0c;有一个热爱骑行的女孩&#xff0c;名叫季思铭&#xff0c;目前是中国农业…

CDA一级备考思维导图

CDA一级备考思维导图 第一章 数据分析概述与职业操守1、数据分析概念、方法论、角色2、数据分析师职业道德与行为准则3、大数据立法、安全、隐私 CDA一级复习备考资料共计七个章节&#xff0c;如需资料&#xff0c;请留言&#xff0c;概览如下图&#xff1a; 第一章 数据分析…

【Java】使用IntelliJ IDEA搭建SSM(MyBatis-Plus)框架并连接MySQL数据库

步骤 0 准备工作1 创建Maven项目2 配置Maven依赖3 配置数据源4 项目结构5 创建实体类6 创建数据访问层7 创建服务层8 创建Controller层9 启动项目10 使用Postman测试接口 0 准备工作 下载并安装 IntelliJ IDEA下载并安装 MySQL 数据库下载并安装Postman测试工具使用 Navicat 创…

WebSocket了解

一.什么是WebSocket WebSocket是HTML5下一种新的协议&#xff08;websocket协议本质上是一个基于tcp的协议&#xff09;它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽并达到实时通讯的目的Websocket是一个持久化的协议 二.websocket的原理 web…