前言

最近一直在写kchat即时通讯软件,在项目中,使用到了LiveData,这篇文章主要来介绍一下LiveData的使用技巧,以及某些问题的解决方案。

什么是LiveData?

LiveData 是 Android Jetpack 提供的一种数据持有类,它可以感知生命周期并自动管理 UI 更新。LiveData 主要用于将数据传递给 UI 层,并确保数据更新时,只有处于活动状态的界面组件(如 ActivityFragment)才会收到通知,从而避免内存泄漏和不必要的 UI 更新。

  1. 生命周期感知LiveData 会自动与生命周期(如 onStartonStop)绑定,只有在相关组件活跃时才会发送更新。
  2. 数据持有:它持有数据并提供观察者机制,UI 可以观察 LiveData 并在数据变化时更新界面。
  3. 线程安全LiveData 可以在后台线程中更新数据,保证线程安全性,并自动切换到主线程进行 UI 更新。
  • setValue(T value):在主线程中更新数据,通知观察者。
  • postValue(T value):在非主线程中更新数据,通知观察者(其原理就是自动切换到主线程进行发送value)。
  • observe(LifecycleOwner owner, Observer<T> observer):注册观察者,监听数据变化。

LiveData的简单使用

下面我拿我开发中的一个’小栗子’——修改密码功能来演示怎么进行使用LiveData

  1. 一般来说在Android项目中LiveData一般都是配合ViewModel使用的,我们新建一个UserViewModel然后继承自 ViewModel(),我这里的代码其实写的不规范(违反了依赖倒置原则),只是给大家演示一下

    • 在上图中我们使用viewModelScope这个是可以使用在viewModel里的协程,然后在协程里面,将调用网络接口返回的数据post到livedata中
    • 向livedata中发送数据的话有两种方法,一种是上图中的,另一种就是_updatePasswordResult.value = result,他俩的区别在于,postValue是发生在主线程中的,然后setValue不是在主线程中的
    • 我们可以看到有下图这种写法,为什么要这样写呢,其实kotlin中的get方法是直接自动获取原属性的值的,但是通过get方法的数据无法进行修改,这样保证了数据的一致性与完整性,在我看来,这也符合mvvm架构中的数据单项流通性
  2. 在activity中声明viewModel,在这里直接使用委托

    private val userViewModel: UserViewModel by viewModels() 
  3. 监听数据

  4. 发送网络请求

解决“多次返回重复数据”问题

举个例子,假设用户进入一个聊天页面后,聊天数据通过LiveData进行观察。此时数据更新正常。但如果用户进入另一个页面后再返回聊天页面触发了第二次监听,LiveData会重复通知他们相同的数据。随着页面跳转的次数增加,这个问题会变得更加明显,导致UI更新过多,影响性能和用户体验。

问题原因:

导致这个问题的根本原因在于我们新建了一个LiveData,并只向其中发送了一个数据。在这个过程中,没有新的数据进入该LiveData替代原有数据,导致这个数据一直存在于LiveData中,同时我们又对LiveData进行了多次监听,就导致该问题的发生。由于LiveData本身的特点,它会保持该数据直到被其他数据更新或者被移除,因此当Activity重新进入时,会重复监听到同样的数据。

解决方法:

我最初的想法是:是否能让LiveData在数据被消费后当条数据立即“消失”,避免重复通知。通过查阅资料,我找到了Google大神提供的方案,虽然最初是Java版的实现,但我将其转换为了Kotlin版本(详见下文的解决方案部分)。 另外,我也曾尝试向GPT寻求帮助,然而得到的答案让我有些失望。GPT的建议是:每次接收到数据后,移除对LiveData的监听。但是,这个做法在我看来并不合理,因为移除监听会导致每次重新设置监听,这样会造成不必要的性能浪费,特别是在频繁需要监听数据变化的场景下。相比之下,Google的方案提供了更优雅的解决方式,通过对LiveData的改造,确保数据只会在被明确消费后触发更新,从而避免了数据的重复通知。
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 * <p>
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 * <p>
 * Note that only one observer is going to be notified of changes.
 */
class SingleLiveEvent<T> : MutableLiveData<T>() {
    private val mPending = AtomicBoolean(false)

    override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
        super.observe(owner) { t ->
            // 只有在 mPending 为 true 时才会触发回调
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        }
    }

    @MainThread
    override fun setValue(value: T?) {
        mPending.set(true)
        super.setValue(value)
    }

    @MainThread
    fun call() {
        setValue(null)
    }
}