10、DataBinding与ViewModel:ViewModel的集成、LiveData与DataBinding联动、SavedStateHandle与状态保存、ViewModelProvider的配置
好,咱们今天来聊聊DataBinding和ViewModel这对黄金搭档。说实话,我早期做Android开发的时候,Activity里塞满了各种状态变量和生命周期回调,代码写得跟意大利面条似的。后来用了ViewModel + DataBinding,才真正体会到什么叫「清爽」。这一章,我会把ViewModel怎么跟DataBinding配合、LiveData怎么联动、状态怎么保存这些实战经验,掰开了揉碎了讲给你听。
10.1 ViewModel的集成:为什么需要它?
ViewModel的核心作用就一句话:在配置变更(比如屏幕旋转)时保住数据。你想想看,以前我们在Activity里写个列表数据,转一下屏就没了,还得用onSaveInstanceState去存,烦不烦?ViewModel就是来解决这个痛点的。
核心要点:ViewModel的生命周期比Activity/Fragment长,它会在配置变更后继续存活,直到对应的LifecycleOwner彻底销毁。
我在项目中遇到过这样一个场景:用户在一个多步骤的表单里填了半天信息,结果一个不小心旋转了屏幕,所有数据全丢了。那用户体验,简直灾难。后来用ViewModel承载表单数据,再配合DataBinding双向绑定,旋转屏幕后数据纹丝不动。嗯,这才是该有的样子。
10.2 LiveData与DataBinding联动
LiveData和DataBinding的联动,说白了就是让UI自动响应数据变化。你不需要手动调用setText()、setVisibility()这些方法,数据一变,界面自己就跟着变了。
怎么实现呢?三步走:
- 在ViewModel中定义LiveData类型的属性
- 在XML布局中绑定这个LiveData
- 设置LifecycleOwner,让DataBinding知道什么时候该更新UI
来看代码:
// ViewModel中定义LiveData
class MyViewModel : ViewModel() {
private val _userName = MutableLiveData<String>()
val userName: LiveData<String> get() = _userName
fun updateName(name: String) {
_userName.value = name
}
}
XML布局里这样绑定:
<layout>
<data>
<variable
name="viewModel"
type="com.example.MyViewModel" />
</data>
<TextView
android:text="@{viewModel.userName}"
... />
</layout>
Activity或Fragment中设置:
binding.viewModel = myViewModel
binding.lifecycleOwner = viewLifecycleOwner // 关键!
个人经验:我建议你总是显式设置lifecycleOwner,哪怕在某些场景下不设也能跑。因为不设的话,DataBinding不知道什么时候该观察LiveData,容易导致内存泄漏或者UI不更新。我曾经踩过这个坑,排查了半天才发现是忘了设lifecycleOwner。
10.3 SavedStateHandle与状态保存
LiveData + ViewModel能应对配置变更,但进程被系统杀掉呢?比如用户把App切到后台,系统内存不足把你的进程干掉了。这时候ViewModel也跟着没了,数据就丢了。
怎么办?用SavedStateHandle。它是ViewModel的一个救星,能在进程被杀死后恢复数据。
用法很简单,在ViewModel的构造函数里接收SavedStateHandle参数:
class MyViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userName: LiveData<String> = savedStateHandle.getLiveData("userName")
fun saveName(name: String) {
savedStateHandle.set("userName", name)
}
}
你看,代码几乎没变,只是把MutableLiveData换成了savedStateHandle.getLiveData()。系统会自动帮你序列化保存到Bundle里,进程恢复时再读出来。
注意:SavedStateHandle能存的数据类型有限制,跟Bundle一样。复杂的对象需要实现Parcelable或Serializable。我建议你尽量存基本类型或者简单的数据类,太复杂的对象还是用Room或者DataStore来持久化吧。
10.4 ViewModelProvider的配置
说到ViewModelProvider,其实就是怎么拿到ViewModel实例的问题。以前我们用ViewModelProviders.of(),现在官方推荐用by viewModels()委托或者ViewModelProvider构造函数。
最常用的方式:
// Fragment中
private val viewModel: MyViewModel by viewModels()
// Activity中
private val viewModel: MyViewModel by viewModels()
如果需要传参数(比如SavedStateHandle),可以自定义ViewModelProvider.Factory:
class MyViewModelFactory(
private val savedStateHandle: SavedStateHandle
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(savedStateHandle) as T
}
}
// 使用
val viewModel = ViewModelProvider(
this,
MyViewModelFactory(DefaultSavedStateHandle())
).get(MyViewModel::class.java)
我的习惯:如果ViewModel不需要额外参数,直接用by viewModels()最省事。如果需要传参,我通常会写一个伴生对象的工厂方法,比如companion object { fun create(handle: SavedStateHandle): MyViewModel },然后在Factory里调用它。这样代码更清晰,也方便单元测试。
10.5 知识体系总览
下面这张图帮你理清ViewModel、LiveData、DataBinding、SavedStateHandle之间的关系:
10.6 避坑指南与最佳实践
最后,分享几个我实战中总结出来的要点:
- 不要直接把Activity传给ViewModel——这会导致内存泄漏。ViewModel应该只持有数据的引用,而不是UI的引用。
- LiveData的粘性事件——LiveData在观察时会立即收到当前值,这在某些场景下可能不是你想要的。比如一次性事件(Snackbar提示),我建议用Event包装类来处理。
- SavedStateHandle不是万能的——它适合存少量、简单的状态。大量数据或者复杂对象,还是用Room或者DataStore吧。
- ViewModel的测试——因为ViewModel不依赖Android框架,单元测试非常好写。我习惯把业务逻辑都放在ViewModel里,UI层只做展示。
我曾经踩过的坑:有一次我在ViewModel里直接持有了一个Context引用,结果屏幕旋转后旧的Activity没有被回收,导致内存泄漏。排查了半天才发现是这个问题。记住:ViewModel里不要持有Activity、Fragment、View这些有生命周期的东西。如果非要Context,用AndroidViewModel或者Application Context。
好了,这一章的内容就到这里。ViewModel + DataBinding这套组合拳打好了,你的代码会变得非常干净、可维护。下一章我们会继续深入,看看Navigation组件怎么跟它们配合,实现更复杂的页面导航和数据传递。