什么是 LiveData
LiveData 是基于 Lifecycle 框架实现的生命周期感知型数据容器,能够让数据观察者更加安全地应对宿主(Activity / Fragment 等)生命周期变化。LiveData 主要有两个特点:
- 会自动取消订阅,当宿主生命周期进入 DESTROYED 状态时,LiveData 会自动移除观察者,避免内存泄漏
- 会安全地回调数据,当宿主生命周期状态低于 STAETED 时,LiveData 不会回调数据;当宿主生命周期不低于 STAETED 时,LiveData 就会重新尝试回调最新的数据。
LiveData 的使用
基本使用
一般情况下,我们使用 LiveData 和 MutableLiveData 两个类。区别是 LiveData 是不可变的,而 MutableLiveData 是可变的,我们可以使用 setValue (主线程使用) 和 postValue (子线程使用)来更新值。代码示例如下:
kotlin
class MyViewModel: ViewModel() {
private val _liveData: MutableLiveData<Int> = MutableLiveData()
val liveData: LiveData<Int>
get() = _liveData
fun update(value: Int) {
_liveData.value = value
}
fun postUpdate(value: Int) {
//在子线程更新值,需要使用 postValue
_liveData.postValue(value)
}
}
class MainActivity : ComponentActivity() {
val myViewModel by viewModels<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
...
myViewModel.liveData.observe(this) {
println("data = $it")
}
//模拟更新数据
thread {
Thread.sleep(1000)
println("set data 1")
myViewModel.postUpdate(1)
println("set data 2")
myViewModel.postUpdate(2)
}
// 启动其他 Activity
...
}
}
当 MainActivity 启动其他 Activity 后,我们在子线程里面更新 liveData 里面的值是不会回调的。当从其他 Activity 回到 MainActivity 时,这时才会回调通知。需要注意的是,如果存在多个值,只会回调最新值,上面的示例就只会回调通知 data 为 2。
observeForever
如果你想要当数据发生变化时,无论页面处于什么状态我们都能收到通知,这时可以使用 observeForever
方法。但是,需要注意一定要记得调用 removeObserver
方法来停止对 LiveData 的观察,否则可能造成内存泄漏。
kotlin
val observer = { result: Int ->
println("data = $result")
}
//注册监听
myViewModel.liveData.observeForever(observer)
//移除监听
myViewModel.liveData.removeObserver(observer)
MediatorLiveData
MediatorLiveData 是一个可以监听其他 LiveData 数据变化的 LiveData。当其他 LiveData 调用 onChange 方法时,会调用设置的 lambda 函数。代码示例如下:
kotlin
val mediatorLiveData = MediatorLiveData<Int>()
mediatorLiveData.addSource(myViewModel.liveData) {
//当 myViewModel.liveData 值变更时,会回调这里
println("mediatorLiveData addSource data = $it")
//主动更新自己的值
mediatorLiveData.value = it
}
mediatorLiveData.observe(this) {
//主动更新自己的值时,才会回调这里
println("mediatorLiveData data = $it")
}
虽然开发中 MediatorLiveData 比较少见,但是实际上它用的地方非常多。比如 LiveData 的转换操作 map、switchMap 都是通过 MediatorLiveData 来实现的。
map
map 操作符可以将某种 LiveData 类型的数据转换为另一种类型的。代码示例如下,将学生类型的 LiveData 转化为分数类型的 LiveData,减少不必要的耦合。
kotlin
private var _studentLiveData = MutableLiveData<Student>()
val score: LiveData<Int>
get() = _studentLiveData.map { it.score }
map 的使用介绍完了,现在直接来看它的源码。如下所示,map 的实现其实很简单,就是使用 MediatorLiveData。当原 LiveData 的数据变更时,就会回调到 transform 方法。当原 LiveData 的 value 只经过 transform 转化后,就将获取的值设置给 MediatorLiveData,通知更新。
kotlin
fun <X, Y> LiveData<X>.map(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards Y)
): LiveData<Y> {
val result = MediatorLiveData<Y>()
if (isInitialized) {
result.value = transform(value as X)
}
result.addSource(this) { x -> result.value = transform(x) }
return result
}
switchMap
switchMap 的源码如下所示:
kotlin
fun <X, Y> LiveData<X>.switchMap(
transform: (@JvmSuppressWildcards X) -> (@JvmSuppressWildcards LiveData<Y>)?
): LiveData<Y> {
val result = MediatorLiveData<Y>()
var liveData: LiveData<Y>? = null
if (isInitialized) {
val initialLiveData = transform(value as X)
if (initialLiveData != null && initialLiveData.isInitialized) {
result.value = initialLiveData.value
}
}
result.addSource(this) { value: X ->
val newLiveData = transform(value)
if (liveData !== newLiveData) {
if (liveData != null) {
result.removeSource(liveData!!)
}
liveData = newLiveData
if (liveData != null) {
result.addSource(liveData!!) { y -> result.setValue(y) }
}
}
}
return result
}
相比于 map 扩展函数,switchMap 函数的源码就比较难懂了。我这里画了一个草图,方便理解。其实 switchMap 方法创建的 MediatorLiveData 监听了两个 LiveData,一个是原来的LiveData,一个是通过 transform 函数生成的 LiveData(这里叫做A LiveData)。
可以看到,MediatorLiveData 在两种情况下会通知更新:一种是原 LiveData 更新导致生成新的 A LiveData ,还有一种就是 A LiveData 自身的 Value 更新。
distinctUntilChanged
当我们往 LiveData 设置相同的数据时,LiveData 不会进行重复判断。因此即使是相同的值,它也会调用 onChange 方法来通知监听。
如果我们需要过滤掉重复的数据怎么办?这时就可以使用 distinctUntilChanged 方法。它内部会使用 MediatorLiveData 来过滤掉重复的值。代码示例如下:
kotlin
val stateLiveData: LiveData<Int>
get() = _liveData.distinctUntilChanged()
自定义操作符
从上面可以看到使用 MediatorLiveData,可以方便做很多转化操作。下面我们就可以使用 MediatorLiveData 实现一个组合效果。代码如下所示:
kotlin
fun <X> LiveData<X>.combine(otherLiveData: LiveData<X>): LiveData<X> {
val outputLiveData = MediatorLiveData<X>()
outputLiveData.addSource(this, outputLiveData::setValue)
outputLiveData.addSource(otherLiveData, outputLiveData::setValue)
return outputLiveData
}
SavedStateHandle
ViewModel + LiveData 的组合想必大家都使用过。该方案解决了在 Android 中因配置更改界面销毁时,数据的保存问题。但是对于系统资源限制而导致的 Activity 被销毁时,该方案就无能为力了。一般情况下遇到这种问题,我们只能通过使用 onSaveInstanceState
和 onRestoreInstanceState
方法来保存和恢复数据。不过 Google 早就想到了这个问题,提供了 SavedStateHandle 来完善这最后一块拼图。
代码示例如下:
kotlin
//一般情况下,我们使用 SavedStateHandle 作为 MyViewModel 的入参
class MyViewModel(val savedStateHandle: SavedStateHandle): ViewModel() {
companion object {
const val KEY = "SAVED_STATE_KEY"
}
//获取对应 value 的LiveData
private val savedStateLiveData
get() = savedStateHandle.getLiveData<String>(KEY)
fun setSavedStateLiveData(value: String) {
//SavedStateHandle 通过key-value的形式保存数据
savedStateHandle[KEY] = value
}
}
//对于入参为 SavedStateHandle 的 ViewModel,viewModels 委托做了处理,
//不需要我们自定义 Factory
val myViewModel by viewModels<MyViewModel>()
//保存数据
myViewModel.setSavedStateLiveData("test_data")
SavedStateHandle
会在 Activity
被销毁时通过onSaveInstanceState(Bundle)
方法将数据保存在 Bundle
中,在重建时又将数据从 onCreate(Bundle?)
中取出,开发者只负责向 SavedStateHandle
存取数据即可,并不需要和 Activity
直接做交互,从而简化了整个开发流程
LiveData 的缺陷
数据丢失
LiveData 有两种情况会导致数据丢失。一种是观察者绑定的生命周期处于非活跃状态时,连续使用 setValue() / postValue() 设置数据时,观察将无法接收到中间的数据。这时造成了数据丢失。
另一种就是调用 postValue 造成的数据丢失。要了解为什么 postValue 方法会造成数据丢失,我们就需要先看看 LiveData 的源码,如下所示:
java
final Object mDataLock = new Object();
static final Object NOT_SET = new Object();
// 临时变量
volatile Object mPendingData = NOT_SET;
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
Object newValue;//注释1
synchronized (mDataLock) {
newValue = mPendingData;
// 重置临时变量
mPendingData = NOT_SET;
}
// 真正修改数据的地方,也是统一到 setValue() 设置数据
setValue((T) newValue);
}
};
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
// 临时变量被重置时,才会发送修改的 Message,这是出现背压的第 1 种情况
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {//注释2
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
假设当主线程执行到注释1 时,这时 mPendingData 不为 NOT_SET,这个时候调用 postValue 方法,postTask 为 false,执行到注释2,退出,这个数据就丢失了。假设当子线程执行到注释2时,其他子线程执行postValue 也会丢掉数据。
数据重放问题
LiveData 的数据重放问题也叫作数据倒灌、粘性事件。它是指新观察者监听一个 LiveData 时,LiveData 会重新分发数据给这个新观察者。代码示例如下:
javascript
myViewModel.liveData.observe(this) {
println("liveData observe1 data = $it")
}
myViewModel.update(1)
myViewModel.liveData.observe(this) {
println("liveData observe2 data = $it")
}
打印结果为:
ini
liveData observe1 data = 1
liveData observe2 data = 1
为什么会产生这种情况呢,这是因为 LiveData 和观察者各自会持有一个版本号 version,每次调用 setValue 或 postValue 方法后,LiveData 持有的版本号会自增 1。在 LiveData 尝试分发数据时,会判断观察者持有版本号是否小于 LiveData 的版本号,如果成立则说明这个观察者还没有消费最新的数据版本。而观察者的持有的初始版本号是 -1,因此当注册新观察者并且正好宿主的生命周期是大于等于可见状态(STARTED)时,就会尝试分发数据,这就是造成数据重放的原因。
总结
当 Activity/Fragment 不处于活跃状态时,LiveData 不会回调数据,而当 Activity/Fragment 重新活跃时,LiveData 就会重新尝试回调最新的数据;当宿主生命周期进入 DESTROYED 状态时,LiveData 也会自动移除观察者,避免内存泄漏。通过 LiveData ,我们在处理数据时可以减少其生命周期的关心,把重心放在具体的业务上来。
但是 LiveData 也有一些缺陷,比如数据丢失 和数据重放问题。因此在 Android 中,目前是更推荐使用 Flow (ShareFlow、StateFlow)来替代 LiveData。不过至于要不要使用 LiveData ,还是要看具体的业务场景,根据自己的需要来选择。