LiveData 概览
LiveData 是 Android Jetpack 组件库中提供的一种可观察的数据存储类。与传统的观察者模式实现不同,LiveData 具备生命周期感知能力(Lifecycle-aware)。
当观察者(Observer)的生命周期处于 STARTED 或 RESUMED 状态时,LiveData 会认为该观察者处于活跃状态。此时,LiveData 才会将数据更新通知给活跃的观察者。如果观察者处于非活跃状态(如 PAUSED 或 DESTROYED),则不会收到更改通知。
开发者可以注册与实现了 LifecycleOwner 接口的对象配对的观察者。这种机制确保了当对应的 Lifecycle 对象状态变为 DESTROYED 时,观察者会被自动移除。这对于 Activity 和 Fragment 特别有用,因为它们可以放心地观察 LiveData 对象,而不必担心因内存泄漏导致的崩溃问题。
LiveData 核心优势
- 数据符合页面状态:确保 UI 仅在页面可见且可用时更新,避免无效渲染。
- 防止内存泄露:自动管理观察者生命周期,无需手动注销。
- 避免崩溃:不会因为 Activity 停止或配置变更导致空指针或非法状态异常。
- 简化生命周期处理:不再需要手动在 onDestroy 中移除监听器。
- 保持数据最新:始终持有最新值,新注册的观察者能立即获取当前状态。
- 资源共享:支持多个视图组件共享同一份数据源,实现状态同步。
LiveData 基础使用
通常建议在 ViewModel 中创建 LiveData 对象,然后在 Activity 或 Fragment 的 onCreate 方法中注册监听。虽然 onStart 和 onResume 也可以,但在 onCreate 中注册更符合生命周期管理的最佳实践,避免冗余调用。
LiveData 简单使用示例
以下是一个倒计时场景的演示。在 ViewModel 中启动一个 2000 秒的倒计时,通过 LiveData 回调给 Activity 进行界面更新。
1. ViewModel 代码
class CountDownModel : ViewModel() {
val countDownLiveData = MutableLiveData<String>()
private var remainSecond = 2000
init {
val countDown = object : CountDownTimer(2000 * 1000, 1000) {
override fun onTick(millisUntilFinished: Long) {
remainSecond--
countDownLiveData.postValue("剩余:${remainSecond} 秒")
}
override fun onFinish() {
countDownLiveData.postValue("倒计时结束")
}
}
countDown.start()
}
}
2. Activity 中观察数据更新 UI 代码
val countDownModel: CountDownModel by viewModels<CountDownModel> {
ViewModelProvider.NewInstanceFactory()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_count_down)
countDownModel.countDownLiveData.observe(this) { value ->
value?.let {
tvCountdownRemainsecond.text = it
}
}
}
使用全局 LiveData 在多个视图监听状态
本例实现的 demo 效果是:创建一个全局的倒计时,在 Activity 中添加两个按钮,点击后可以切换 FragmentA 和 FragmentB。通过全局自定义 LiveData 单例实现数据监听,切换 Fragment 后,页面上会展示倒计时的剩余秒数。
注意:使用单例模式管理 LiveData 时需小心内存泄漏风险,建议结合 Application Context 或依赖注入框架使用。
1. 全局自定义 LiveData 代码
class GlobalLiveData : LiveData<String>() {
val countManager = CountDownManager()
val listener = object : OnDataChangeListener {
override fun change(data: String) {
postValue(data)
}
}
override fun onActive() {
super.onActive()
countManager.setListener(listener)
}
override fun onInactive() {
super.onInactive()
countManager.removeListener(listener)
}
companion object {
@Volatile
private lateinit var globalData: GlobalLiveData
fun getInstance(): GlobalLiveData {
if (!::globalData.isInitialized) {
synchronized(this) {
if (!::globalData.isInitialized) {
globalData = GlobalLiveData()
}
}
}
return globalData
}
}
}
2. 倒计时器代码片段
private val listeners = mutableListOf<OnDataChangeListener>()
init {
val countDown = object : CountDownTimer(2000 * 1000, 1000) {
override fun onTick(millisUntilFinished: Long) {
remainSecond--
callback("剩余:${remainSecond} 秒")
}
override fun onFinish() {
callback("倒计时结束")
}
}
countDown.start()
}
private fun callback(msg: String) {
for (listener in listeners) {
listener.change(msg)
}
}
3. FragmentA、FragmentB 中监听倒计时状态
GlobalLiveData.getInstance().observe(viewLifecycleOwner) { t ->
inflate.findViewById<TextView>(R.id.tv_fragmentA).text = "fragmenta:${t}"
}
GlobalLiveData.getInstance().observe(viewLifecycleOwner) { t ->
inflate.findViewById<TextView>(R.id.tv_fragmentB).text = "fragmentb:${t}"
}
最终效果显示,当我们切换 Fragment 的时候,两个 Fragment 显示的秒数是一致的。即使启动一个新的 Activity 去查看剩余秒数,数据也是同步的。
对 LiveData 进行转换
map 和 switchMap 两个方法可以对已有的 LiveData 进行转换得到新的 LiveData,常用于数据预处理。
Transformation.map
在 Activity 中观察 ViewModel 中的数据更新,当点击 Activity 中按钮的时候会调用 ViewModel.sendData 方法发送数据,然后发送的数据会做一定的转换给 Activity,Activity 打印日志展示。
1. 创建 ViewModel,Model 中创建 LiveData
class TransMapViewModel : ViewModel() {
fun sendData() {
userLiveData.value = User("李白", 1200)
}
val userLiveData = MutableLiveData<User>()
val mapLiveData = Transformations.map(userLiveData) { user ->
"${user.name} : ${user.age}"
}
}
data class User(var name: String, var age: Int)
代码中 mapLiveData 是对 userLiveData 进行转换得到的,所以当我们调用 sendData 方法更新 userLiveData 中的方法时,mapLiveData 的回调也会触发。
2. 在 Activity 中观察 mapLiveData 并点击按钮发送小数据
mapViewModel.mapLiveData.observe(this) { data ->
logEE(data)
tvMap.text = data
}
btnMap.setOnClickListener {
mapViewModel.sendData()
}
Transformation.switchMap
SwitchMap 适用于当 LiveData 的值变化时,需要返回另一个 LiveData 对象的场景。它会自动订阅最新的 LiveData 并取消旧订阅。
1. ViewModel 中代码
class SwitchMapViewModel : ViewModel() {
fun sendData() {
userLiveData.value = SwitchUser("李白", 1200)
}
private val userLiveData = MutableLiveData<SwitchUser>()
val mapLiveData = Transformations.switchMap(userLiveData) { switchUser ->
changeUser(switchUser!!)
}
private fun changeUser(it: SwitchUser): LiveData<String> {
return MutableLiveData("${it.name} 的名字杜甫知道")
}
}
data class SwitchUser(var name: String, var age: Int)
2. 调用部分代码
model.mapLiveData.observe(this) { result ->
logEE(result)
}
btnSwitchMap.setOnClickListener {
model.sendData()
}
合并两个 LiveData(MediatorLiveData)
想象这样一个场景:您的 App 里面有一个评论列表的功能,可以对列表内容进行点赞。每一个点赞都是一个异步任务,产品需求并不想让用户点太多赞,比如一分钟点赞数量不能超过 10 次。这种场景很适合用 LiveData 的合并功能。
我们模拟这样一个事情:界面上有两个按钮,点一次相当于点赞一次,我们点击十次按钮就在界面上展示文字提示用户已经点击了十次数据。
1. Model 代码
class MediatorLiveViewModel : ViewModel() {
var count = 0
fun setData1(name: String) {
liveData1.value = name
}
fun setData2(age: Int) {
liveData2.value = age
}
private val liveData1 = MutableLiveData<String>()
private val liveData2 = MutableLiveData<Int>()
val liveCombind = MediatorLiveData<String>()
init {
liveCombind.addSource(liveData1) {
increase()
}
liveCombind.addSource(liveData2) {
increase()
}
}
private fun increase() {
count++
if (count == 10) {
liveCombind.value = "安安安安卓同学,您已经点击 ${count} 次,再点我也不跟你玩了,收手吧。。。"
}
}
}
Model 中创建了三个 LiveData,其中两个分别是 liveData1 和 liveData2,分别对应其中两个按钮。还有一个 liveCombind 用来回调超过十次调用的场景。
Init 方法中 liveCombind.addSource 调用就是表示用来中间拦截 liveData1 和 liveData2 的数据更新,处理 count 累加和是否回调 liveCombind 的功能。
2. Activity 中代码
model.liveCombind.observe(this) { message ->
logEE(message)
tvCount.text = message
}
btnLiveData1.setOnClickListener {
model.setData1("李白")
}
btnLiveData2.setOnClickListener {
model.setData2(1000)
}
observeForever 的使用与风险
observeForever 方法也是注册 LiveData 监听的方法,表示即使页面被覆盖处于不活跃状态也可以收到数据改变的回调。该方法绑定的观察者不会随生命周期自动移除,必须手动调用 removeObserver 否则会导致内存泄漏。
livedata.observeForever(observer)
override fun onDestroy() {
super.onDestroy()
livedata.removeObserver(observer)
}
LiveData 和协程联合使用
emit 方式使用
有时候你可能需要处理异步任务,任务处理完成后刷新 UI。这种情况可以使用 LiveData 的扩展程序实现。
1. 引入依赖插件
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
2. 开启异步任务方法
本例我们实现下面的逻辑:在 ViewModel 中阻塞 4s,然后通知 Activity。
fun startAsyncWithSecond(second: Int): LiveData<String> = liveData {
delay(second * 1000L)
emit("倒计时结束")
}
当我们调用 startAsyncWithSecond 方法的时候会马上返回一个 LiveData 对象,供我们注册监听。
3. Activity 中注册 LiveData 监听
model.startAsyncWithSecond(3).observe(this) { result ->
logEE(result)
}
emitSource 使用
使用 emitSource 的效果等同于 MediatorLiveData 的效果,可以将其他 LiveData 作为数据源嵌入到当前的 LiveData 中。
1. 创建异步任务方法
fun startAsyncEmitSource(second: Int) = liveData<String> {
delay(second * 1000L)
emit("${second} 秒阻塞完成,再阻塞三秒后通知你")
val emitSourceLiveData = MutableLiveData<String>()
emitSource(emitSourceLiveData)
delay(second * 1000L)
emitSourceLiveData.value = "再次阻塞 ${second} 秒完成"
}
2. Activity 中注册监听
model.startAsyncEmitSource(3).observe(this) { result ->
logEE(result)
}
LiveData 最佳实践与常见问题
- 线程安全:
setValue 必须在主线程调用,postValue 可以在子线程调用但会排队到主线程执行。
- 空值处理:建议使用 Kotlin 的可空类型,并在 Observer 中使用
?.let 或 Elvis 运算符处理 null 值。
- 配置变更:利用 ViewModel + LiveData 组合,可以完美解决屏幕旋转导致的数据丢失问题。
- 避免静态引用:不要在静态变量中直接持有 Activity 或 Fragment 实例,应使用 WeakReference 或依赖注入。
- 性能优化:对于大量数据的列表更新,考虑使用 PagedList 或 Flow 替代 LiveData。
总结
LiveData 是 Android 开发中管理 UI 状态的重要工具。通过理解其生命周期感知机制,合理使用转换方法和协程集成,可以构建出健壮、易维护的应用架构。在实际开发中,应遵循最佳实践,避免常见的内存泄漏陷阱,确保用户体验流畅稳定。