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 三者如何协同工作?

好,三个组件都讲完了。它们怎么配合呢?我画个图你就明白了。

MVVM 核心组件协作图 Activity / Fragment ViewModel Repository / Model DataBinding LiveData 持有ViewModel引用 观察数据变化 绑定数据到视图 获取数据 Activity持有ViewModel → ViewModel通过LiveData通知Activity → DataBinding自动更新UI

流程其实很简单:

  1. Activity/Fragment 创建并持有 ViewModel
  2. ViewModel 通过 Repository 获取数据,用 LiveData 包装
  3. Activity/Fragment 观察 LiveData,数据变化时收到通知
  4. 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