Jetpack 可观察数据容器 LiveData 的入门与基础使用

在 Jetpack 之前的 Android 开发中,界面上显示的内容,都是数据发生变化时,开发者需要主动通知界面进行刷新。但当界面或是业务逻辑变得复杂时,数据状态与界面状态同步也会变得越来越困难,这将带来很大的风险。例如,某些数据变化后遗漏了界面的刷新;或者当 Activity、Fragment 已经销毁时,后台任务仍然尝试更新界面,从而导致异常或内存泄漏。

为了解决这些问题,Google 在 Jetpack 中引入了 LiveData。它是一种具备生命周期感知能力的可观察数据容器,能够帮助开发者更加安全、高效地管理 UI 数据。它能通过一个 observe 方法设置当前数据的观察者,并且在数据更新时自动调用观察者的代码进行更新。并且与传统观察者模式不同,LiveData 能够感知 Activity 和 Fragment 的生命周期。在界面销毁后,它会自动停止数据分发,从而避免许多常见的问题。

那么,LiveData 究竟该如何使用?接下来我们从一个简单的示例开始。另外 LiveData 是建立在 Lifecycle 的基础之上的,因此需要你对 Lifecycle 有所了解:Jetpack 生命周期组件 Lifecycle 的设计思想和使用 - 掘金

引入 LiveData

首先在 gradle/libs.versions.toml 中声明 livedata 库:

ini 复制代码
[versions]
lifecycleLivedataKtx = "2.10.0"

[libraries]
androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }

在 module 的 build.gradle 中引入依赖:

kotlin 复制代码
dependencies {
    implementation(libs.androidx.lifecycle.livedata.ktx)
}

前面说到 LiveData 能够感知生命周期,其实就意味着 livedata 是依赖 lifecycle 库的,因此当你依赖了 livedata 库,那么 lifecycle 以及相关 lifecycle 基础组件都会被 Gradle 自动传递依赖进来,因此你不需要额外再添加 Lifecycle 的相关依赖。

引入后,我们就开始 LiveData 的第一个示例。

LiveData 的第一个示例

LiveData 的使用方式非常简单。首先创建一个用于保存数据的 MutableLiveData 对象,然后为它注册一个观察者。当数据发生变化时,观察者会自动收到通知。

创建 LiveData

这里我们创建一个类 LiveDataContainer,用它来存放我们创建的 MutableLiveData:

kotlin 复制代码
class LiveDataContainer {
    val counter = MutableLiveData<Int>(0)
    fun startCount() = thread {
        while (true) {
            Thread.sleep(1000L)
            counter.postValue(if (counter.isInitialized) counter.value!! + 1 else 0)
        }
    }
}

这里我们创建了一个 MutableLiveData,也就是一个可变的 LiveData,并添加了一个方法,此方法每一秒将这个 MutableLiveData 加 1。可见这是一个简单的计时器,下面我们来看这个计时器如何使用。

为 LiveData 添加观察者

使用 LiveData 的方法很简单,主要就是调用其 observe() 方法。下面我们在一个 ComponentActivity 中使用它,这里注意,这个 ComponentActivity 是实现了 LifecycleOwner 的:

kotlin 复制代码
class MainActivity : ComponentActivity() {

    private val dataContainer = LiveDataContainer()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView = findViewById<TextView>(R.id.text_view_1)

        dataContainer.counter.observe(this, object : Observer<Int> {
            override fun onChanged(value: Int) {
                textView.text = "当前秒数 : $value"
            }
        })
        dataContainer.startCount()
    }
}

如上,一个简单的 LiveData 的示例就完成了。运行起来后就可以看到当前秒数在不断增加,是不是很简单。

MutableLiveData 与 LiveData

细心的读者可能已经发现,在上面的例子中,我们创建的是 MutableLiveData:

kotlin 复制代码
val counter = MutableLiveData<Int>(0)

而本文介绍的却是 LiveData,那么 MutableLiveData 和 LiveData 之间究竟是什么关系呢?

实际上,就像是 MutableList 与 List 类似,MutableLiveData 也继承自 LiveData,两者最大的区别在于是否允许修改数据。

LiveData 只提供了数据观察能力,并没有暴露数据的修改能力,如果你观察这个类,会发现它的 setValue 和 postValue 方法都是 protected 权限:

kotlin 复制代码
protected void postValue(T value)
@MainThread protected void setValue(T value)

而在 MutableLiveData 中,这两个方法的权限都被修改为 public:

kotlin 复制代码
@Override
public void postValue(T value) {
    super.postValue(value);
}

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

因此 LiveData 中的数据是只读的,它只负责观察数据;而 MutableLiveData 中的数据可读可写,它负责修改数据。

在实际开发中,通常会在类的内部使用 MutableLiveData 保存数据,而对外只暴露 LiveData:

kotlin 复制代码
private val _counter = MutableLiveData<Int>(0)
val counter: LiveData<Int> get() = _counter

这样,外部在使用时只能观察数据,却无法直接修改数据,从而保证数据只能由拥有者进行修改,避免外部随意改变状态。

不过在本篇文章中,为了便于演示,我们暂时直接使用 MutableLiveData。在使用 Jetpack 的开发中,你会经常看到这种封装方式。

setValue 和 postValue

前面的示例中,我们使用了如下代码更新计数器:

kotlin 复制代码
counter.postValue(counter.value!! + 1)

这里调用的是 postValue,而不是直接修改 value。虽然 setValue 和 postValue 都能更新 LiveData 中保存的数据,但使用场景却有所不同。

setValue() 用于在主线程中更新数据。当调用 setValue() 后,LiveData 会立即更新内部保存的数据,并通知所有处于活跃状态的观察者。

postValue() 可以在任意线程中调用。当调用 postValue() 时,LiveData 并不会立即更新数据,而是将更新任务切换到主线程执行,最终仍然通过内部的 setValue() 完成数据更新。

在此示例中,因为计时器运行在子线程中,因此需要使用 postValue()。

LiveData 的生命周期感知

现在我们回到 observe 这个方法上,这个方法 observe 的原型为:

kotlin 复制代码
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)

这个方法需要两个参数,第二个参数就是 LiveData 的观察者,就是我们在 MainActivity 传入的修改界面的代码,而第一个参数,是一个生命周期持有者 LifecycleOwner,而 LiveData 感知的生命周期,就是这个参数的生命周期。或者说,第一个参数 owner 告诉 LiveData,第二个参数 observer 观察者属于这个 owner。

那么 LiveData 如何感知生命周期呢?

这里需要大家对 Lifecycle 这个库有一个了解,如果不熟悉 Lifecycle 可以看一看这篇文章:Jetpack 生命周期组件 Lifecycle 的设计思想和使用

简单来说,就是 LifecycleOwner 中有一个 Lifecycle,在这个 Lifecycle 对象中,可以获取到组件的当前状态。LiveData 会获取 LifecycleOwner 中持有的 Lifecycle 对象,并监听 Lifecycle 的状态变化,在对应的状态时自动执行相关操作。

LifecycleOwner 本身并不负责分发生命周期事件,它只是持有一个 Lifecycle 对象,而 Lifecycle 本身是一个可被观察的生命周期管理器。LiveData 在 observe 方法中会调用 owner.getLifecycle().addObserver() 注册一个生命周期观察者,从而监听 Lifecycle 状态的变化。

生命周期组件有以下几种状态:

复制代码
INITIALIZED
CREATED
STARTED
RESUMED
DESTROYED

而其中 STARTED,RESUMED 属于活跃状态,LiveData 只有在组件在活跃时才分发数据。例如,当 Activity 进入后台时,其生命周期会降到 CREATED 状态,此时 LiveData 不会继续向该 Activity 分发数据;当 Activity 再次回到前台时,又会重新开始接收数据。在本示例中,如果按下 Home 键将应用切到后台,计数器虽然仍在继续运行,但界面不会收到新的数据更新,当应用重新回到前台时,LiveData 会再次开始分发数据。

当 LifecycleOwner 进入 DESTROYED 状态时,LiveData 会自动移除与该 LifecycleOwner 关联的 Observer。同时,它也会注销自身在 Lifecycle 中注册的监听,从而避免内存泄漏。

这也是 LiveData 与传统观察者模式最大的区别。在传统观察者模式中,开发者通常需要手动取消注册观察者;而 LiveData 会根据 LifecycleOwner 的生命周期自动完成注册与注销工作。

LiveData 的核心原理总结

通过上面的例子,可以看出,LiveData 本质上就是一个观察者模式的实现,其内部主要有两个重要的成员变量:

  • 每一个 LiveData 对象中,会使用一个名为 mData 的 Object 对象来保存当前数据,然后通过泛型 T 进行类型约束。
  • 不仅如此,其内部还有一个名为 mObservers 的集合,当我们调用 observe 方法时,实际上就是在这个 mObservers 集合中注册了一个观察者。
  • 当调用 LiveData 的 setValue 或 postValue 时,mData 数据发生变化,mObservers 集合中的观察者就会被自动执行。

这就是 LiveData 最核心的工作方式:数据发生变化时,自动通知所有正在观察它的对象。 并且 LiveData 会观察 LifecycleOwner 的当前状态,在活跃状态下分发数据,在 DESTROY 时,则会移除相关的观察者。

相关推荐
问心无愧05132 小时前
ctf show web入门261
android·前端·笔记
alexhilton2 小时前
车载系统中的可扩展UI:从UI嵌入到系统窗口编排
android·kotlin·android jetpack
Cloud_Shy6182 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第一章 Item 4 - 6)
android·数据库·论文阅读·python
therese_100862 小时前
安卓面试题
android
码云骑士2 小时前
Android Launcher启动过程
android
Java面试题总结3 小时前
MySQL EXISTS 详解:存在性判断、NOT EXISTS 与实战示例
android·数据库·mysql
_李小白4 小时前
【android opencv学习笔记】Day 30: 滤波算法之拉普拉斯算子
android·opencv·学习
NiceCloud喜云12 小时前
Opus 4.8 的 Effort Control 怎么选:Low 到 Max 五档策略
android·java·大数据·前端·c++·python·spring
日光明媚16 小时前
一步生成视频!One-Forcing:DMD + 零成本 GAN,训练 200 步超越多步 SOTA
android·开发语言·kotlin