observeForever是否具备生命周期感知?MVVM下ViewModel与Firebase交互方案咨询
1. observeForever() 到底有没有生命周期感知能力?
明确回答:完全没有。observeForever() 是个“死心眼”的观察者——它会一直盯着LiveData的变化,不管你的Activity/Fragment是处于前台、后台还是已经销毁,都不会自动停止监听。
回到你的代码:虽然ViewModel的onCleared()方法会在页面销毁时被调用,但如果你没手动调用removeObserver()把这个永久观察者删掉,它会一直赖着不走,大概率会造成内存泄漏。原因很简单:Repository里的LiveData还攥着这个观察者的引用,ViewModel虽然被标记为可回收,但观察者本身还占着资源呢。
2. 不用给ViewModel附加生命周期,怎么让Repository和ViewModel正确通信?
你现在用observeForever()中转数据的方式确实不太理想,其实有两种更规范的方案,完全不用碰observeForever():
方案一:直接把Repository的LiveData抛给UI层
既然你不需要转换Device对象,ViewModel根本没必要多此一举包一层MutableLiveData,直接把Repository返回的LiveData暴露给UI就行,代码瞬间清爽:
改进后的ViewModel:
class MainViewmodel: ViewModel() { private val repo = Repo() // 啥都不用做,直接返回Repo的LiveData fun fetchDeviceData(deviceId:String): LiveData<Device> { return repo.getDeviceData(deviceId) } }
UI层代码完全不用改:
fun observeData(deviceId:String){ viewModel.fetchDeviceData(deviceId).observe(this, Observer { textView.text = "Tipo: ${it.devType}" }) }
这种方式下,UI层的observe()会自动处理生命周期——页面销毁时自动移除观察者,Repository里的LiveData因为没了活跃观察者,也会乖乖进入闲置状态,不会浪费资源。
方案二:用自定义LiveData封装Firebase监听(更推荐)
如果后续你需要监听Firebase数据的实时变化,自定义LiveData能帮你自动管理监听的生命周期,再也不用手动加监听、删监听:
改进后的Repository:
class Repo { private val db = FirebaseDatabase.getInstance().reference fun getDeviceData(deviceId:String): LiveData<Device> { return object : LiveData<Device>() { private lateinit var listener: ValueEventListener override fun onActive() { super.onActive() // 只有当有UI在观察时,才添加Firebase监听 listener = object : ValueEventListener { override fun onDataChange(dataSnapshot: DataSnapshot) { val device = dataSnapshot.getValue(Device::class.java) value = device } override fun onCancelled(dataError: DatabaseError) { Log.e("Error","handle error callback") } } // 如果要单次监听就用addListenerForSingleValueEvent,实时监听就用addValueEventListener db.child(deviceId).child("config/device").addListenerForSingleValueEvent(listener) } override fun onInactive() { super.onInactive() // 没人观察了?立刻把Firebase监听删掉,省资源 db.child(deviceId).child("config/device").removeEventListener(listener) } } } }
这种自定义LiveData的好处太明显了:
- 完全贴合LiveData的生命周期感知逻辑,ViewModel和UI层啥都不用管
- 灵活切换单次/实时监听,改个方法名就行
- 从根源上避免了内存泄漏的可能
最后说下你现有代码的坑
你现在的ViewModel用了observeForever()但没在onCleared()里移除观察者,这绝对是个内存泄漏隐患。如果实在要保留这种中转方式(真心不推荐),一定要手动移除:
class MainViewmodel: ViewModel() { private val repo = Repo() // 得把观察者存起来,不然找不到它来移除 private lateinit var deviceObserver: Observer<Device> private lateinit var repoLiveData: LiveData<Device> private lateinit var mutableData: MutableLiveData<Device> fun fetchDeviceData(deviceId:String):LiveData<Device>{ mutableData = MutableLiveData<Device>() repoLiveData = repo.getDeviceData(deviceId) deviceObserver = Observer<Device> { mutableData.value = it } repoLiveData.observeForever(deviceObserver) return mutableData } override fun onCleared() { super.onCleared() // 必须手动移除,不然观察者会一直活着 repoLiveData.removeObserver(deviceObserver) } }
但还是那句话,这种方式既麻烦又容易出错,不如直接用前面两种方案来得稳妥。
内容的提问来源于stack exchange,提问作者Gastón Saillán

