LiveData
简述:
数据实时更新、安全更新:当liveData的setValue被调用时,会遍历自身所有observer并considerNotify,此时会判断observer的活跃状态(shouldBeActive)和内部版本,决定是否向其发送通知。
避免内存泄漏:传入lifeCycleOwner即走observe()的LiveData会在lifeCycleOwner回调onStateChanged为DESTROYED的时候移除掉observer防止内存泄露,而相对的observeForever则由于没有owner而无此特性。
解决Configuration Change问题、粘性数据:一般而言observer是在onCreate中调用的,liveData.observer方法在调用时也会立即被推送liveData中最后一次数据。(会导致所谓的数据倒灌)。Configuration Change时会ViewModel会从当前Activity或fragmentManager的viewModelStore的缓存中重新取出,而ViewModel中的liveData自然也会保留。
https://zhuanlan.zhihu.com/p/593472898
Featrue
UI和实时数据保持一致 因为LiveData采用的是观察者模式,这样一来就可以在数据发生改变时获得通知,更新UI。
避免内存泄漏 观察者被绑定到组件的生命周期上,当被绑定的组件销毁(destroy)时,观察者会立刻自动清理自身的数据。
不会再产生由于Activity处于stop状态而引起的崩溃,例如:当Activity处于后台状态时,是不会收到LiveData的任何事件的。
不需要再解决生命周期带来的问题 LiveData可以感知被绑定的组件的生命周期,只有在活跃状态才会通知数据变化。
实时数据刷新 当组件处于活跃状态或者从不活跃状态到活跃状态时总是能收到最新的数据。
解决Configuration Change问题 在屏幕发生旋转或者被回收再次启动,立刻就能收到最新的数据。
常用实现类
https://chatgpt.com/c/21580ee8-b685-48be-a510-712388ae346e
LiveData常用于MVVM中,ViewModel向View层提供订阅的单向数据源。一般实践中会根据”单一数据源真相”原则,在ViewModel层中收敛所有的可变MutableLiveData,只对View层提供不可变的LiveData。View层行为应通过调用ViewModel的方法变更数据。
在MVVM架构中,View层不应该直接改变ViewModel中的数据状态。这是为了保持清晰的职责分离和确保数据的一致性。具体来说:
- View层:主要负责显示数据和处理用户交互。它可以观察
LiveData
并根据数据变化更新UI。- ViewModel层:负责准备和管理与UI相关的数据。它通过
MutableLiveData
更新数据,并暴露LiveData
给View层以供观察。- Model层:负责数据操作,如网络请求和数据库操作。
工作流程
- View观察ViewModel中的LiveData:View层通过观察ViewModel中暴露的
LiveData
,接收数据变化并更新UI。- 用户交互通过View通知ViewModel:当用户与UI交互时,View层调用ViewModel中的方法,传递用户动作或输入。
- ViewModel更新数据:ViewModel处理用户的输入,进行必要的业务逻辑处理,并通过更新
MutableLiveData
来改变数据状态。- View层收到更新并更新UI:由于View观察了
LiveData
,当ViewModel中的数据状态发生变化时,View会自动收到通知并更新UI。假设你有一个按钮点击事件来更新数据:
1
2
3
4
5
6
7
8 // 在View层(例如Activity或Fragment)中
viewModel.buttonClicked.observe(this, Observer { newText ->
textView.text = newText
})
button.setOnClickListener {
viewModel.onButtonClicked()
}在ViewModel中:
1
2
3
4
5
6
7
8
9 class TestViewModel : ViewModel() {
private val _buttonClicked: MutableLiveData<String> = MutableLiveData()
val buttonClicked: LiveData<String> = _buttonClicked
fun onButtonClicked() {
// 更新数据
_buttonClicked.value = "按钮点击了"
}
}关键点
- View层不直接修改数据:View层通过调用ViewModel中的方法来请求数据更新,而不是直接修改
LiveData
。- ViewModel负责数据更新:ViewModel接收View层的请求,更新
MutableLiveData
中的数据。- 数据变化通知View层:
LiveData
的数据变化会通知观察者(View层),并更新UI。通过这种方式,确保了View层与ViewModel层之间的职责清晰分离,并且数据状态的管理是集中且可控的。
LiveData
不可变的(一般作为ViewModel对View层提供的单向数据源)
MutableLiveData
可变的(一般用于ViewModel层内部变更数据,会用对外提供LiveData同名加下划线前缀命名)
https://developer.android.com/reference/android/arch/lifecycle/MutableLiveData
MediatorLiveData
可变的、监听多源的
https://developer.android.com/reference/android/arch/lifecycle/MediatorLiveData
原理
我们知道 livedata 的使用很简单,它是采用观察者模式实现的
- 添加观察者
- 在数据改变的时候设置 value,这样会回调 Observer 的 onChanged 方法
1 | public interface Observer<T> { |
observe方法
LiveData包含两个用于添加数据观察者(Observer)的方法,分别是
- observe(LifecycleOwner , Observer)
生命周期安全的
- observeForever(Observer)
两个方法的区别对于外部来说只在于是否提供了生命周期安全的保障。
生命周期安全的observe
1 |
|
传入的LifecycleOwner
参数意味着携带了Lifecycle对象,LiveData内部就根据 Lifecycle的生命周期事件的回调变化在合适的时机进行数据通知,并在 Lifecycle对象处于DESTROYED状态时自动移除Observer,这也是LiveData避免内存泄漏的最重要的一个点。
上面的代码使用到了LifecycleBoundObserver
,它是抽象类ObserverWrapper的实现类。ObserverWrapper用于包装外部传进来的Observer对象,为子类定义好特定的抽象方法和共用逻辑,主要是提供了共用的状态分发函数。
1 | class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { |
LifecycleBoundObserver会保证当LiveData依赖的生命周期转到非活跃时,不进行通知(如果DESTROY会直接移除Observer);当转到活跃时且数据版本更新了则向其发起回调。
LifecycleBoundObserver的整个事件流程是这样的:
- Lifecycle的生命周期发生变化,从而回调了onStateChanged函数
- onStateChanged函数首先判断Lifecycle是否已处于DESTROYED状态,是的话则直接移除Observer,整个回调流程结束,否则则继续以下流程
- onStateChanged调用了activeStateChanged()函数,activeStateChanged()函数判断Lifecycle的活跃状态是否发生了变化,如果从非活跃状态切换到了活跃状态,是的话则调用dispatchingValue()函数来分发值,最终再根据ObserverWrapper内部的value版本号mLastVersion来判断是否有新值需要向其回调,是的话则向其回调新值,否则则返回
1 | private abstract class ObserverWrapper { |
ObserverWrapper一共有两个子类:
LifecycleBoundObserver和AlwaysActiveObserver,两者的差别就在于是否和生命周期相绑定。
非生命周期安全的observeForever
1 |
|
上面代码使用到了AlwaysActiveObserver,它也是抽象类ObserverWrapper的实现类,其shouldBeActive()返回值固定为true,意味着只要有数据变化都会进行回调。所以使用observeForever()函数一定要在过后主动移除Observer
,避免内存泄露和NPE。
更新LiveData的值
更新LiveData的值的方法一共有两个,分别是:
- setValue(T value)
- postValue(T value)
setValue
setValue(T)函数被限定在只能主线程进行调用。
1 | /** |
dispatchingValue()
函数设计得比较巧妙,用两个全局的布尔变量mDispatchingValue和mDispatchInvalidated就实现了新旧值判断、旧值舍弃、新值重新全局发布的逻辑。
1 | //initiator 为 null 则说明需要遍历回调整个 mObservers |
1 | // 判断是否要将数据分发到指定的 ObserverWrapper |
postValue
1 | /** |
postValue(T)函数不限定调用者所在线程,不管是主线程还是子线程都可以调用,因此是存在多线程竞争的可能性的,postValue(T)函数的重点旧在于需要理解其从子线程切换到主线程之间的状态变化。
在mPostValueRunnable被执行前,所有通过postValue(T)函数传递的value都会被保存到变量mPendingData上,且只会保留最后一个
,直到mPostValueRunnable被执行后mPendingData才会被重置,所以使用 postValue(T) 函数在多线程同时调用或者单线程连续调用的情况下是存在丢值(外部的 Observer 只能接收到最新值)的可能性的。
Issue
粘性事件
LiveData 本身被设计为粘性事件,也即,一旦 LiveData 持有数据,那么在观察者订阅该 LiveData 时,会被推送最后一次数据。(当observe时会立即回调onChange推送数据)
数据倒灌(其实就是粘性事件,在多级页面中)
当页面重建时或多级fragment订阅其activity的ShareViewModel的liveData时 被 LiveData 回推脏数据
https://xiaozhuanlan.com/topic/6719328450
e.g:
设想一下这样的场景,一级页面是列表,二级页面是只读的详情预览,三级页面是可编辑的详情,
当我们在编辑页修改完信息时,我们通常会通过回调来通知一二级页面刷新信息。此时如是通过 LiveData 来通知,便存在隐患,
因为跨页面通信使用的是 Activity 级作用域的 SharedViewModel,其生命周期长于 Fragment,乃至当 “编辑页”、“预览页” 出栈时,SharedViewModel 和其旗下的 LiveData 还存在于内存中,
那么下一次从 “列表页” 跳到 “预览页”,“预览页” 一注册 LiveData,就会因为粘性设定,而收到上一次从 “编辑页” 发来的旧信息,这明显不符合预期,
现有解决方案及各自缺陷
或应参考这篇文章https://juejin.cn/post/7268622342728171572,其中评论中提出
个人见解,
方式是好方式,看了许多解决数据倒灌的文章,但是数据倒灌感觉并不是一个问题。
livedata本身是用于描述状态的,而不是事件的。
描述事件一般用publishsubject或shareflow或eventbus,再不济自己写个listener也行。 //ps:上flow
因此感觉用livedata描述事件本身违背了livedata的设计初衷。
在《Jetpack MVVM 精讲》中我分别提到了 Event 事件包装器、反射方式、SingleLiveEvent 这三种方式来解决 “数据倒灌” 的问题。它们分别来自上文我们提到的外网、美团的文章,和官方最新 demo。
分别存在如下问题:
Event 事件包装器
对于多观察者的情况,只允许第一个观察者消费,这不符合现实需求;
而且手写 Event 事件包装器,在 Java 中存在 null 安全的一致性问题。
反射干预 Version 的方式:美团LiveDataBus
存在延迟,无法用于对实时性有要求的场景;
并且数据会随着 SharedViewModel 长久滞留在内存中得不到释放。
https://github.com/JeremyLiao/LiveDataBus
官方最新 demo 中的 SingleLiveEvent (已移除)
是对 Event 事件包装器 一致性问题的改进,但未解决多观察者消费的问题;
而且额外引入了消息未能从内存中释放的问题。
UnPeekLiveData 特点
UnPeekLiveData 通过 独创的 “延时自动清理消息” 的设计,来满足:
1.消息被分发给多个观察者时,*不会因第一个观察者消费了而直接被置空*
2.时限到了,*消息便不再会被倒灌*
3.时限到了,*消息自动从内存中清理释放*
4.使非入侵的设计成为可能,并最终结合官方 SingleLiveEvent 的设计实现了 ***遵循开闭原则的非入侵重写***。
连续postValue()吞数据
Google在postValue的注释上说明了
If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.
如果在主线程执行一个已发布的任务之前多次调用此方法,则只会分派最后一个值。
只有第一次调用时会走到postToMainThread(mPostValueRunnable),后面所有执行的postValue在执行完postTask = mPendingData == NOT_SET 和 mPendingData = value 后,直接由于之后postTask == false,所以return掉了,也就不会再postToMainThread一次而是只修改mPendingData数据。
直到主线程中mPostValueRunable执行方法中会将真正调用setValue并将mPendingData重置为NOT_SET.
这个实现其实听起来很像View的刷新,比如TextView连续设置两次文本,也是只有当Vsync到来时取最新的文本设置到TextView中