3. MVVM核心组件:ViewModel、LiveData、DataBinding的原理与基本使用
好,咱们今天聊聊MVVM的三个核心组件。说实话,这三个东西刚出来那会儿,我也觉得挺绕的。ViewModel是干嘛的?LiveData和普通的回调有啥区别?DataBinding是不是又回到了JSP时代?
别急,我一个一个给你拆开讲。讲完你会发现,其实没那么玄乎。
3.1 ViewModel:你的数据救生员
先说说ViewModel。我刚开始做Android开发那会儿,最头疼的就是屏幕旋转。Activity一重建,数据全没了。那时候的解决方案是什么?onSaveInstanceState?那玩意儿存个字符串还行,存个列表?算了吧。
ViewModel的核心作用,说白了就是帮你在配置变更时保住数据。它会在Activity/Fragment的整个生命周期里存活,不会因为屏幕旋转就被销毁。
关键点:ViewModel的生命周期比Activity长。Activity重建时,ViewModel还是原来那个对象。
来看个最简单的例子:
class CounterViewModel : ViewModel() {
private var count = 0
fun getCount(): Int = count
fun increment() { count++ }
}
在Activity里用起来也很简单:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: CounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(CounterViewModel::class.java)
println("当前计数:${viewModel.getCount()}")
}
}
嗯,这里要注意:千万不要把Context传给ViewModel。为什么?因为ViewModel比Activity活得久,你传个Activity进去,内存泄漏就来了。我当年就踩过这个坑,查了半天才发现是ViewModel里持有了Activity引用。
我的习惯:ViewModel里只放业务逻辑和数据,UI相关的东西一概不碰。如果需要Context,用AndroidViewModel,它内部持有Application的引用。
3.2 LiveData:观察者模式的优雅实现
ViewModel把数据保住了,但怎么通知UI更新呢?你可能会说:回调啊。没错,但回调有个问题——生命周期管理。Activity在前台时更新UI,在后台时就不该更新。LiveData就是来解决这个问题的。
LiveData的本质,是一个可观察的数据持有者。它感知生命周期,只在活跃状态下通知观察者。
什么叫活跃状态?就是STARTED或RESUMED。说白了,界面可见时才更新。
改造一下上面的例子:
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData<Int>(0)
val count: LiveData<Int> get() = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
}
Activity里观察数据变化:
viewModel.count.observe(this) { newCount ->
textView.text = "计数:$newCount"
}
你看,代码简洁多了。而且LiveData会自动处理生命周期,Activity销毁时自动解绑,不会出现空指针。
我曾经踩过的坑:LiveData的setValue()必须在主线程调用。如果你在子线程更新数据,用postValue()。postValue()是线程安全的,但要注意它可能会丢失中间值——如果你连续post两次,第二次可能覆盖第一次。
3.3 DataBinding:告别findViewById
DataBinding这个东西,刚出来时争议挺大的。有人说它让XML变得复杂,有人说它把逻辑写到了视图层。我个人觉得,用好了是真香。
DataBinding的核心作用:把数据和UI直接绑定,省去你写findViewById和setText的功夫。
先要在build.gradle里开启:
android {
...
buildFeatures {
dataBinding = true
}
}
然后改造XML布局:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.example.CounterViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(viewModel.count)}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.increment()}"
android:text="增加" />
</LinearLayout>
</layout>
Activity里绑定:
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewModel = viewModel
binding.lifecycleOwner = this // 让LiveData感知生命周期
你看,不用写findViewById,不用写setText,不用写setOnClickListener。数据变了,UI自动更新。
我的建议:DataBinding适合用在数据展示类的页面,比如列表、详情页。如果页面逻辑特别复杂,用DataBinding反而会让XML变得臃肿。这时候可以考虑用ViewBinding,它只做绑定不做数据驱动,更轻量。
3.4 三者如何协同工作?
好,三个组件都讲完了。它们怎么配合呢?我画个图你就明白了。
流程其实很简单:
- Activity/Fragment 创建并持有 ViewModel
- ViewModel 通过 Repository 获取数据,用 LiveData 包装
- Activity/Fragment 观察 LiveData,数据变化时收到通知
- DataBinding 把 LiveData 和 XML 绑定,自动更新 UI
你想想看,这套流程下来,Activity的代码是不是少了很多?它只需要做一件事:创建ViewModel,然后观察数据。至于数据怎么来的、怎么存的,那是ViewModel的事。UI怎么更新?那是DataBinding的事。
核心思想:各司其职。Activity管生命周期,ViewModel管业务逻辑,LiveData管数据通知,DataBinding管UI更新。谁也别越界。
3.5 实际项目中的最佳实践
说了这么多原理,来点实际的。我在项目里总结了几条经验:
| 组件 | 最佳实践 | 常见坑 |
|---|---|---|
| ViewModel | 不要持有Context引用;用ViewModelProvider获取 | 在ViewModel里做网络请求,忘记取消 |
| LiveData | 暴露不可变的LiveData,内部用MutableLiveData | 在子线程调用setValue()导致崩溃 |
| DataBinding | 配合LiveData使用,自动感知生命周期 | XML里写复杂逻辑,难以调试 |
我曾经犯过的错:刚开始用LiveData时,我在ViewModel里暴露了MutableLiveData。结果Fragment直接修改了数据,绕过了ViewModel的业务逻辑。后来我学乖了,永远只暴露LiveData,内部用MutableLiveData。
嗯,这三个组件讲完了。说白了,MVVM就是让代码更清晰、更可测。ViewModel负责逻辑,LiveData负责通知,DataBinding负责绑定。各干各的,互不干扰。
下一章咱们聊聊更进阶的话题——如何用Kotlin协程配合ViewModel做异步任务。到时候你会发现,ViewModel和协程简直是天生一对。
公众号:蓝海资料掘金营,微信deep3321