Android 架构组件之 LiveData

2024-02-25 16:58
文章标签 android 组件 架构 livedata

本文主要是介绍Android 架构组件之 LiveData,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

    • 1. LiveData 是什么?为什么要使用 LiveData?
      • 1.1 一个示例,看使用 LiveData 的优势
    • 2. 分析 LiveData 的组成及实现原理
      • 2.1 通过类图分析 LiveData 的组成
      • 2.2 分析 LiveData 是怎样感知组件生命周期的
      • 2.3 数据更新时 LiveData 又是怎样通知组件的
    • 3. LiveData 中的转换方法
      • 3.1 map
      • 3.2 switchMap
      • 3.3 MediatorLiveData
    • 4. 总结使用 LiveData 的步骤
    • 参考链接

LiveData 是一种具有生命周期感知能力的、可观察数据的、持有类。本文将从以下几个方面来介绍 LiveData:

  • LiveData 是什么?为什么要使用 LiveData?
  • 分析 LiveData 的组成及实现原理
  • 介绍 LiveData 中的转换方法
  • 总结使用 LiveData 的步骤

1. LiveData 是什么?为什么要使用 LiveData?

LiveData 可以简单的理解为具有感知生命周期能力的容器。生命周期感知能力的实现,得益于 Android Framework 使用了一下 Lifecycles 中的类。关于 Lifecycle 的详细介绍,可以查看此前的一篇文章 Android 架构组件之 Lifecycle

这里我们简单的总结一下,Lifecycle 组件包括三个组成部分:

  • Lifecycle:Lifecycle是一个定义 Android 生命周期及状态的类。
  • LifecycleOwner 接口,用于连接有生命周期的对象,例如 AppCompatActivity 和 Fragment。
  • LifecycleObserver,用于观察 LifecycleOwner 的接口。

而我们要介绍的 LiveData,是一个 LifecycleObserver,它可以直接感知 Activity 或 Fragment 的生命周期。基于此,使得 LiveData 较其它的容器类,有一下两个优势:

  • 如果Activity 不在屏幕上,LiveData 不会触发没有必要的界面刷新;
  • 如果 Activity 已经被销毁,LiveData 将自动清空与 Observer 的连接。这样屏幕外或者已经销毁 Activity 或 Fragment就不会被意外地调用。

1.1 一个示例,看使用 LiveData 的优势

下面来看一个示例。假设有一个 UI 界面和 LiveData 对象,这个对象保存了你想要在屏幕上显示的数据,界面可以声明“观察”LiveData 对象,也就是说,界面希望在 LiveData 有更新时收到通知,随后界面会使用新数据重新进行绘制。简而言之,LiveData 可以使屏幕上显示的内容与数据随时保持同步。 看示例代码:

LiveData 对象通常保存在 ViewModel 类中,关于 ViewModel 的介绍,可以查看Android 架构组件之 ViewModel

class LiveDataTimerViewModel : ViewModel() {private val mElapsedTime = MutableLiveData<Long>()private var mInitialTime: Long = 0private var timer: Timer? = nullinit {mInitialTime = SystemClock.elapsedRealtime()timer = Timer()// Update the elapsed time every second.timer?.scheduleAtFixedRate(object : TimerTask() {override fun run() {val newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000// setValue() cannot be called from a background thread so post to main thread.mElapsedTime.postValue(newValue)}}, ONE_SECOND, ONE_SECOND)}fun getElapsedTime(): LiveData<Long> = mElapsedTime

在 ViewModel 中,声明一个 MutableLiveData 类型的成员变量,用来保存屏幕上要显示的数据,通过 Timer 类,每秒钟更新一次 LiveData 中的数据。

接下来,在 Activity 的 onCreate()方法中,我们可以从 ViewModel 中获取 LiveData,并在 LiveData 上调用 observe()方法,方法中的第一个参数为 Context,在这里为当下的 Activity,第二个参数是“Observer 观察者”,属于方法回调,回调之后界面会被更新,如果需要更新 LiveData,可以调用 setValue()方法或 postValue()方法。

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.chrono_activity_3)mLiveDataTimerViewModel = ViewModelProvider(this).get(LiveDataTimerViewModel::class.java)subscribe()}private fun subscribe() {val elapsedTimeObserver = Observer<Long> {val newText = resources.getString(R.string.seconds, it)timer_textview.text = newTextLog.d("ChronoActivity3", "Updating timer");}mLiveDataTimerViewModel?.getElapsedTime()?.observe(this, elapsedTimeObserver)}

完整的示例代码,在文末给出 GitHub 地址。

setValue()方法或 postValue()方法,两者的区别在于,setValue 只可以在主线程上运行,postValue 只可以在后台线程上运行,调用 setValue 和 postValue 时,LiveData 将通知 Observer,并更新界面。

以上是LiveData的基本介绍以及使用它的优势,除此之外。Room 数据框架可以很友好地支持 LiveData,Room 返回的 LiveData 对象,可以在数据库数据变化时自动收到通知,还可以在后台线程中加载数据,这样我们可以在数据库更新的时候,轻松地更新界面。关于 Room 组件的介绍,可以查看我们的系列文章。

2. 分析 LiveData 的组成及实现原理

2.1 通过类图分析 LiveData 的组成

我们用最新的 lifecycle-livedata:2.2.0-alpha02 版本来分析其原理,先看一张类图。

通过上面的类图,我们可以看到:
  • 在 LiveData 中定义了三个内部类 ObserverWrapper、LifecycleBoundObserver 和 AlwaysActiveObserver。

  • ObserverWrapper 是 Observer 的一个包装类,在该类中会记录组件是否处在激活状态的信息,shouldBeActive() 方法是一个抽象方法,需要子类去实现,activeStateChanged() 方法会根据组件的状态,回调不同的方法,后面会详细介绍。

  • LifecycleBoundObserver 和 AlwaysActiveObserver 是 ObserverWrapper的子类,LiveData 对组件的注册监听,都是通过这两个类完成的,区别是当使用 observe()方法注册时使用的是 LifecycleBoundObserver,使用observeForever()方法注册时使用的是 AlwaysActiveObserver。LifecycleBoundObserver 还实现了 LifecycleEventObserver 接口,这样LifecycleBoundObserver 就可以通过 onStateChanged()回调方法来监测组件生命周期的变化了。

  • LiveData 是一个抽象类,它有两个子类 MutableLiveData 和 MediatorLiveData,在 LiveData 中有个两个用来完成对组件注册监听的方法 observe() 和 observeForever();有注册监听的方法,就有取消注册监听的方法 removeObserver() 和 removeObservers();因为 LiveData 是一个容器,所以提供了两个设置 Value 的方法 postValue() 和 setValue();onActive() 和 onInactive()是两个回调方法。

  • LiveData 中的两个回调方法 onActive()和 onInactive(),当LiveData 监听的处于激活状态的组件中 observer的个数不为 0 时会调用回调函数onActive(),当 LiveData监听的组件没有处于激活状态,或者组件中 observer 的个数为 0 时调用onInactive()回调函数。

2.2 分析 LiveData 是怎样感知组件生命周期的

在上面的类图中,我们了解了跟 LiveData 相关的类,下面我们来看一下 LiveData 是怎样感知组件生命周期的,当数据更新时又是怎样通知组件的。

先来看 LiveData 是怎样感知组件生命周期的,上面提到了在 LiveData 中有两个用来完成对组件注册监听的方法 observe() 和 observeForever(),先来看 observe()方法:

@MainThread
public 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;}owner.getLifecycle().addObserver(wrapper);}

observe 的接收一个 LifecycleOwner对象,组件 Activity 和 Fragment 都是实现了LifecycleOwner接口,所以这里的owner可以理解为组件对象。在方法内部创建了LifecycleBoundObserver 对象 wrapper,通过

owner.getLifecycle().addObserver(wrapper)

完成了对组件生命周期的监听,当组件生命周期发生变化时都会回调 wrapper 对象中的onStateChanged()方法,再来看一下 LifecycleBoundObserver 的实现:

class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {removeObserver(mObserver);return;}activeStateChanged(shouldBeActive());}@Overrideboolean isAttachedTo(LifecycleOwner owner) {return mOwner == owner;}@Overridevoid detachObserver() {mOwner.getLifecycle().removeObserver(this);}}

可以看到在 LifecycleBoundObserver 中有一个 shouldBeActive()方法,这个方法会在组件状态是STARTED 和 RESUMED 时返回 true。

在 onStateChanged() 方法中会调用到 activeStateChanged()方法,该方法接收一个 boolean 型参数,当参数值为 true 时会调用dispatchingValue()方法,这个方法最终会调用 Observer 的onChanged()回调方法。这也就解释了使用 observe()方法对组件进行监听,只有组件在激活状态STARTED、RESUMED时(对应生命周期 onStart、onResume、onPause)时,才会收到数据更新的通知。当组件在DESTROYED状态时,在onStateChanged()方法内部会调用removeObserver(),因此我们在使用 observe()方法时,可以不考虑 observer 的销毁。

void activeStateChanged(boolean newActive) {if (newActive == mActive) {return;}// immediately set active state, so we'd never dispatch anything to inactive// ownermActive = newActive;boolean wasInactive = LiveData.this.mActiveCount == 0;LiveData.this.mActiveCount += mActive ? 1 : -1;if (wasInactive && mActive) {onActive();}if (LiveData.this.mActiveCount == 0 && !mActive) {onInactive();}if (mActive) {dispatchingValue(this);}}

我们前面提到使用 observeForever 注册时需要手动取消,来看看是为什么。

@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {assertMainThread("observeForever");AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing instanceof LiveData.LifecycleBoundObserver) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}wrapper.activeStateChanged(true);}

observeForever() 方法不再接收 LifecycleOwner 对象,在AlwaysActiveObserver 类中的 shouldBeActive()方法的返回值一直是 true,所以使用该方法进行注册监听时,只要数据有变化,不论组件处在什么状态,都会收到通知,这也是为什么要在退出组件时,需要手动调用 removeXXX()方法将注册的 observer 移除。

private class AlwaysActiveObserver extends ObserverWrapper {AlwaysActiveObserver(Observer<? super T> observer) {super(observer);}@Overrideboolean shouldBeActive() {return true;}}

2.3 数据更新时 LiveData 又是怎样通知组件的

了解了 LiveData 是怎样感知组件生命周期的,下面再来看一下当数据更新时又是怎样通知组件的。LiveData 提供了两个方法来更新数据,setValue() 方法和 postValue() 方法。区别是 setValue() 方法要在主线程中调用,而 postValue() 方法既可在主线程也可在子线程中调用。先来看一下 setValue() 方法。

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

setValue() 方法中会调用 dispatchingValue() 方法,这个方法最终会调用observe() 或者 observeForever() 传入的Observer对象的 onChanged()回调方法。这就解释了当数据更新时是怎样发送通知的。再来看一下 postValue()方法:

protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);}private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}};

可以看到 postValue() 方法通过 ArchTaskExecutor 实现了在主线程中执行mPostValueRunnable 对象中的内容,而在mPostValueRunnable中最终会调用setValue() 方法来实现改变 LiveData 存储的数据。

至此,我们知道了 LiveData 是怎样感知组件生命周期的,以及当数据更新时又是怎样通知组件的。

3. LiveData 中的转换方法

LiveData 还提供各种转换方法,包括: map() 、switchMap() 和 MediatorLiveData。

3.1 map

map()方法,可转换 LiveData 的输出,map 可以将 LiveData A 的方法传递到 LiveData B。

比如,上面的示例中将 LiveData 中的 Long 类型,通过 map 方法转换为 String 类型。

var userNameLiveData = Transformations.map(mElapsedTime, { time ->"$time 秒"}

3.2 switchMap

switchMap() 更改被 LvieData 观察的对象,switchMap与 map 很相似,区别在于,switchMap 给出的是 LiveData,而 Map 给出的是具体值。

举个例子,假设在 Room 数据库中存有很多用户,我们有一个查找数据库用户的方法,我们可以通过 switchMap 将用户 ID与 LiveData 相连,当界面需要查找的 ID 发生变化时,会重新调用查找用户的方法,调用之后生成 LiveData。现在将引用到新找到的用户的 LiveData,这样不管需要查找多少个用户的 LiveData,界面都只需要观察生成的 LiveData 一次。这就是 switchMap 的强大之处。

3.3 MediatorLiveData

MediatorLiveData:用于自定义数据转换,它可以添加或者移除原来的 LiveData 对象,然后多个原 LiveData 对象可以进行组合,作为单一 LiveData 向下传递。

4. 总结使用 LiveData 的步骤

我们先来总结一下使用 LiveData 的步骤:

  • 创建 LiveData 实例以存储某种类型的数据,通常在 ViewModel 类中完成。

  • 创建可定义 onChanged() 方法的 Observer 对象,该方法在 LiveData 对象存储的数据更改时会被调用。通常情况下,在界面(如 Activity 或 Fragment)中创建 Observer 对象。

  • 使用 observe() 方法将 Observer 对象附加到 LiveData 对象。这样会使 Observer 对象订阅 LiveData 对象,以使其收到有关更改的通知。通常情况下,您可以在界面(如 Activity 或 Fragment)中附加 Observer 对象。

LiveData使用起来非常简单,它可以被观察,可以感知生命周期,在很多情况下都能够被灵活运用。

更多内容,可以订阅 我的博客


参考链接

LiveData

这篇关于Android 架构组件之 LiveData的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/746107

相关文章

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

基于Qt Qml实现时间轴组件

《基于QtQml实现时间轴组件》时间轴组件是现代用户界面中常见的元素,用于按时间顺序展示事件,本文主要为大家详细介绍了如何使用Qml实现一个简单的时间轴组件,需要的可以参考下... 目录写在前面效果图组件概述实现细节1. 组件结构2. 属性定义3. 数据模型4. 事件项的添加和排序5. 事件项的渲染如何使用

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。

百度/小米/滴滴/京东,中台架构比较

小米中台建设实践 01 小米的三大中台建设:业务+数据+技术 业务中台--从业务说起 在中台建设中,需要规范化的服务接口、一致整合化的数据、容器化的技术组件以及弹性的基础设施。并结合业务情况,判定是否真的需要中台。 小米参考了业界优秀的案例包括移动中台、数据中台、业务中台、技术中台等,再结合其业务发展历程及业务现状,整理了中台架构的核心方法论,一是企业如何共享服务,二是如何为业务提供便利。

如何在页面调用utility bar并传递参数至lwc组件

1.在app的utility item中添加lwc组件: 2.调用utility bar api的方式有两种: 方法一,通过lwc调用: import {LightningElement,api ,wire } from 'lwc';import { publish, MessageContext } from 'lightning/messageService';import Ca

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo