本文原始发表于:https://juejin.cn/post/6955727941850365965
LiveData 默认是支持粘性消息的(关于什么是粘性消息,请移步我的另一篇文章:LiveData 的正确使用姿势以及反模式 ),如何通过 LiveData 来实现非粘性消息呢,本文将在官博的基础上,分析几种尝试的方案,以及他们各自的优缺点
姿势一:重置 LiveData 的值
在 observer 里加上一个判断,当 LiveData 的值符合某个条件的时候,才做出响应的更新 UI 逻辑,然后提供一个重置 LiveData 值的方法,重置之后,observer 中条件判断为 fasle,因此可以达到不更新 UI 的目的
示例代码
moneyReceivedViewModel.billLiveData.observe(this, Observer {
if (it != null) {
Toast.makeText(this, "到账$it元", Toast.LENGTH_SHORT).show()
}
})
class MoneyReceivedViewModel : ViewModel {
private val _billLiveData = MutableLiveData<String>()
val billLiveData: LiveData<String> = _billLiveData
// 在 observe 之前和 show Toast 之后重置一下 LiveData
fun reset() {
_billLiveData.value = null
}
}
缺陷:
- 需要在 observer 中增加一些逻辑判断代码,这不符合简洁的 MVVM 模式(不应该在 View 层做过多的逻辑处理)
- 需要手动重置,不够优雅,一旦忘记重置就容易引发问题
姿势二:使用 SingleLiveEvent
SingleLiveEvent 是官方 sample 中封装的 LiveData,可以实现一个事件只被消费一,实现原理也很简单
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending: AtomicBoolean = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(
TAG,
"Multiple observers registered but only one will be notified of changes."
)
}
// Observe the internal MutableLiveData
super.observe(owner, object : Observer<T?> {
override fun onChanged(t: T?) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
setValue(null)
}
companion object {
private const val TAG = "SingleLiveEvent"
}
}
示例代码
class MoneyReceivedViewModel : ViewModel() {
val billLiveEvent = SingleLiveEvent<String>()
fun payForLiveEvent(money: String) {
billLiveEvent.value = money
}
}
viewModel.payForLiveEvent("100")
viewModel.billLiveEvent.observe(this, Observer {
Log.d("sample", "到账 ${it} 元")
})
btn.setOnClickListener {
viewModel.payForLiveEvent("200")
}
btn_wait.setOnClickListener {
viewModel.billLiveEvent.observe(this, Observer {
Log.d("sample", "到账 ${it} 元")
})
}
// 以上代码在 Activity 的 onCreate() 中
// 当 Activity 启动的时候就会输出日志 -> 到账 100 元(没有实现不接收 observe() 之前的事件)
// 点击 btn 之后输出 -> 到账 200 元
// 点击 btn_wait 无输出(实现了事件只被消费一次)
缺陷:
- 由于 setValue() 之后 onChange() 只会回调一次,因此,如果有多个 observer 的话,也只有一个才能收到回调,而且无法保证哪一个 observer 被回调(每个 observer 生命周期不一样,observe() 时机也不一样)
- 在 observe 之前发送的事件还是会被接收到,没有解决问题
姿势三:LiveData 包裹一个 Event
open class Event<out T>(private val content: T) {
var hasBeenConsumed = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun consumed(): T? {
return if (hasBeenConsumed) {
null
} else {
hasBeenConsumed = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peek(): T = content
}
class MoneyReceivedViewModel : ViewModel() {
private val _billEvent = MutableLiveData<Event<String>>()
val billEvent: LiveData<Event<String>>
get() = _billEvent
fun payForEvent(msg: String) {
_billEvent.value = Event(msg)
}
}
viewModel.payForEvent("100")
viewModel.billEvent.observe(this, Observer {
it.consumed()?.let {
Log.d("sample", "到账 ${it} 元")
}
})
btn.setOnClickListener {
viewModel.payForEvent("200")
}
btn_wait.setOnClickListener {
viewModel.billEvent.observe(this, Observer {
it.consumed()?.let {
Log.d("sample", "到账 ${it} 元")
}
})
}
// 以上代码在 Activity 的 onCreate() 中
// 当 Activity 启动的时候就会输出日志 -> 到账 100 元(没有实现不接收 observe() 之前的事件)
// 点击 btn 之后输出 -> 到账 200 元
// 点击 btn_wait 无输出(实现了事件只被消费一次)
这种方式的好处是:
- onChanged() 每次都会回调,但是是否要处理数据取决于 observer:consumed() 不返回已经被消费的消息,peek() 可返回已经被消费的数据
缺陷:
- 和姿势二一样,observe() 之前的数据还是会被监听到,没有解决问题
- 虽然可以添加多个 observers 且使用 peek() 来获取数据,但是还是无法实现多个 observer 都只接收一次事件
姿势四:支持多 observer 且仅接受 observe() 之后的消息
可参考 基于LiveData实现事件总线思路和方案
LiveData 并不是非用不可
我们使用了各种 workaround 的方式让 LiveData 支持粘性消息,以上几种方案也只有最后一种能够解决问题。但是笔者并不推荐使用这样的方式来绕过 LiveData 的限制,去打破 LiveData 原本的设计,这会让 LiveData 变得更让人难以理解
我们并不是非要用 LiveData 不可,LiveData 有适合自己的使用场景(具体可移步:LiveData 的正确使用姿势以及反模式 ),事件总线的场景已经有非常多的优秀开源库可以使用:EventBus、RxBus 等都可以供我们参考。
另一篇官博也提到了,如果我们项目中已经有一些比较成熟的方案,我们大可不必非要使用 LiveData
这里有些人可能会提到现有的 EventBus 或者 RxBus 等都没有生命周期感知能力,不能在生命周期销毁的时候自动解绑监听,而 LiveData 有这个能力,所以想使用 LiveData 来实现事件总线。这里其实我们可以换一种思路:给 callback 或者 EventBus 等增加 Lifecycle 感知能力,这样便能实现自动解绑了,这种方式相比使用 hack 的手段修改 LiveData 会更加友好一些,具体可以参考我的另一篇文章:自定义生命周期以及实现生命周期感知能力
相关文章
使用 Architecture Component 实现 MVVM 的正确姿势
LiveData 的正确使用姿势以及反模式
自定义生命周期以及实现生命周期感知能力