Android 中的 MVVM 是如何构建起来的

本系列为小说《逆袭西二旗》的技术讲解,用于详细说明剧情里涉及的开发细节。

ViewBinding 有什么优势

ViewBinding 是 Android 中用于简化访问布局中 View 过程的一项能力。

它避免手动多次调用 findViewById(),并以更类型安全的方式获取视图,从而减少样板代码并降低运行时错误风险。

面试一问

findViewById() 相比,ViewBinding 在类型安全与空安全上如何改进?

带来的收益是什么?

工作方式

在搞明白上面的问题之前,我们先来看看它的工作方式。

在项目中启用 ViewBinding 后,Android 会为每个 XML 布局文件生成一个 binding 类。该类的命名来自布局文件名:下划线会转换为驼峰,并在结尾追加 Binding

例如,布局为 activity_main.xml 时,生成类为 ActivityMainBinding

binding 类包含布局中各视图的引用,你可以直接访问它们,而无需做类型强转或再调用 findViewById()

kotlin 复制代码
// 在 Activity 中的使用示例
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.textView.text = "Hello, ViewBinding! 关注 RockByte 公众号呀!"
    }
}

上例中,ActivityMainBinding 对应 activity_main.xml 布局。inflate() 用于创建 binding 实例;binding.root 传给 setContentView() 以设置内容视图。

优势

  • Type Safety(类型安全): 直接访问视图、避免强制类型转换,减少类型不匹配带来的运行时问题。
  • Cleaner Code(代码更干净): 减少 findViewById() 等样板。
  • Null Safety(空安全): 对可空视图更友好,降低与可选 UI 交互时出错风险。
  • Performance(性能):DataBinding 不同,ViewBinding 运行时额外开销很小,因为它不处理数据绑定表达式,也不做额外的绑定同步。

对比 DataBinding

DataBinding 能力更强,例如支持绑定表达式、双向数据绑定,但也更复杂并带来运行时成本。而 ViewBinding 更专注于让视图访问更简单、运行更轻。当你不需要更高级的数据驱动绑定时,它更合适。

启用 ViewBinding

build.gradle 中加入如下配置以启用 ViewBinding

gradle 复制代码
android {
    buildFeatures {
        viewBinding = true
    }
}

启用后,项目中的 XML 布局会对应自动生成 binding 类。

补充提示:

在 Google 官方正式支持 ViewBinding 之前,ButterKnife 很常用:通过注解处理将 View 注入到字段,从而绕开 findViewById,并借助类似依赖注入的方式提供类型安全。

它一度是 Android 生态中非常重要的库,也几乎是奠定 Jake Wharton 影响力的重要项目之一,并在 Android 开发里激发了大量实践与变化。

虽然随着 ViewBinding 被官方采纳,ButterKnife 已被标记弃用,但在当时它非常有创新性;对希望了解依赖注入模式的人来说,也仍然值得学习。

总结

ViewBinding 是一种更轻、类型更安全的视图交互方式,也能改善代码安全性。它可作为 findViewById() 的替代方案,当你不需要 DataBinding 的高级能力时,是很合适的选择。

启用 ViewBinding 能简化 UI 相关代码,并提升可维护性与运行时安全。

在使用 XML 布局时,我一定会启用 ViewBinding ,反而是 DataBinding,我几乎不用。

DataBinding 是如何工作的

既然刚学完 ViewBinding ,那么我们立即看看 DataBinding

DataBinding 是 Android 提供的能力,让 XML 布局中的 UI 组件可以直接与数据源建立绑定。它通过减少 findViewById() 等样板,并在 UI 与数据模型间支持更实时的变化同步,为 UI 设计带来更偏"声明式"的写法。

此外,它也在 MVVM(Model-View-ViewModel)架构中扮演重要角色。

大家知道吗? MVVM 最初是由 Microsoft 作为分离 UI 逻辑与业务逻辑的模式提出的。

面试一问

DataBindingViewBinding 的关键差异是什么?如何取舍?

DataBindingMVVM 中扮演什么角色?它如何帮助在 Android 开发里分离 UI 逻辑与业务逻辑?

启用 DataBinding

ViewBinding 类似,在 build.gradle 中加入如下配置以启用 DataBinding

gradle 复制代码
android {
    buildFeatures {
        dataBinding = true
    }
}

工作原理

DataBinding 会对使用 <layout>XML 布局生成 binding 类。该类能直接提供视图访问,并允许在 XML 中使用表达式完成数据绑定。

下面展示一个典型的 DataBinding XML 布局示例:

xml 复制代码
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.rockbyte.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.age}" />
    </LinearLayout>
</layout>

在这个例子里,一个 User 对象与 XML 布局绑定,并在 TextView 中显示 user.nameuser.age

在代码中绑定数据的示例:

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val user = User("Alice", 25)
        binding.user = user
    }
}

data class User(val name: String, val age: Int)

这里将 user 作为布局的数据源,用于完成初始展示;如果希望后续数据变化继续驱动 UI 更新,需要使用 LiveDataObservableFieldBaseObservable 等可观察数据源。

特性

  1. Two-Way Data Binding(双向数据绑定): 在 UI 与数据模型间自动保持同步,常用于表单、输入等场景。

    xml 复制代码
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@={user.name}" />
  2. Binding Expressions(绑定表达式):XML 中写简单逻辑,例如条件判断等。这确实是 DataBinding 强大功能的一个展现。

    xml 复制代码
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.age > 18 ? `Adult` : `Minor`}" />
  3. Lifecycle Awareness(生命周期感知): 当结合 LiveData 并设置 lifecycleOwner 时,可以按生命周期状态观察数据并更新 UI。

优势

  • Reduces Boilerplate(减少样板): 减少 findViewById() 与大量手动更新 UI 的代码。
  • Real-Time UI Updates(更实时的 UI 更新): 可观察数据变化时,UI 能自动体现。
  • Declarative UI(声明式 UI): 将部分展示逻辑放到 XML 中,便于组织复杂布局。
  • Improves Testability(可测试性更好): UI 与代码解耦,有利于分别测试。

缺点

  • Performance Overhead(性能开销): 相比更轻量方案(如仅 ViewBinding)有额外运行时成本。
  • Complexity(复杂度): 小型/简单项目可能带来不必要复杂度。
  • Learning Curve(学习曲线): 需要理解绑定表达式与生命周期等问题。

总结

DataBinding 让你可以把 UI 元素在 XML 中直接绑定到数据源,减少样板,并以更偏声明式的方式管理 UI。

它支持双向绑定、表达式等能力,是动态、交互式界面的强工具。但也会带来复杂性与运行开销;当应用需要更实时、交互性更强的 UI 且希望减少手动控制时,它是很合适的选择。

进阶:ViewBinding 与 DataBinding

ViewBindingDataBinding 都用于减少视图相关样板,但目标与能力集不同。下面是更细的对照说明。

ViewBinding 用于简化在布局中访问 View:为每个带 id 的视图生成更直接的引用,避免 findViewById,提升类型安全并减少重复代码。

ViewBinding 的关键特点:

  • 为各 XML 布局生成 binding 类。
  • 提供对布局中视图的直接引用,绕开 findViewById
  • 通过编译期信息提升类型/空值方面的安全性(相对手写查找)。
  • 不自带绑定表达式/数据驱动更新等高级能力。

DataBinding 更复杂、也更强,它允许把 UI 与数据源在 XML 中绑定,支持绑定表达式、可观察数据、双向数据绑定,因此也更常见于 MVVM 等场景。

DataBinding 的关键特点:

  • XML 中把 UI 与数据源建立绑定关系。
  • 通过表达式在 UI 上表达动态行为。
  • 提供双向数据绑定,让 UI 与数据更实时同步。
  • 可与 LiveDataObservableFieldBaseObservable 等结合做可观察的数据流。

它们之间存在如下的关键差异:

  1. 目的: ViewBinding 偏"更安全的取 View";DataBinding 偏"数据驱动 UI 绑定与更新"。
  2. 生成内容: ViewBinding 主要生成视图引用;DataBinding 还会围绕数据生成额外绑定能力(不止视图引用本身)。
  3. 表达式: ViewBinding 不在 XML 中执行表达式;DataBinding 支持。
  4. Two-Way Data Binding(双向绑定): 只有 DataBinding 提供。
  5. 性能: 通常 ViewBinding 更轻,因为不跑数据绑定那条解析/同步链路。

简单来说。

需要简单取引用、不依赖 findViewById 时,用 ViewBinding (更偏"简单项目/场景")。在复杂数据驱动 UI、或 MVVM 中需要与 LiveData / ObservableField / @Bindable 等结合时,用 DataBinding 更对口。

DataBinding 也带来额外成本;若 ViewBinding 已足够,就没必要为数据绑定多付那部分复杂度。

LiveData

LiveData 是 Jetpack 架构组件里提供的一种可观察数据容器。它具备生命周期感知能力,会关注与之关联的 LifecycleOwner(如 ActivityFragmentviewLifecycleOwner)的生命周期,并倾向于只在组件处于活跃生命周期状态(如 started / resumed 等)时,才把更新投递给 UI,从而降低不合适时机更新 UI 带来的风险。

LiveData 的核心用途之一,是让 UI 能观察数据变化,并在数据变化时自动自我更新。这也是 Android 上实现更"响应式" UI 的重要工具之一。

LiveData 的一些优势包括:

  1. Lifecycle Awareness(生命周期感知): 在组件不活跃时减少不必要更新,有助于降低崩溃与泄漏风险等。
  2. Automatic Cleanup(自动清理): 与某个生命周期绑定的观察者在销毁时更易于被处理。
  3. Observer Pattern(观察者模式): 数据变化时,可通过观察者让 UI 自动跟随更新。
  4. Thread Safety(线程安全设计): 设计上也考虑到来自后台线程更新数据等需求。

以下示例展示如何在 ViewModel 中用 LiveData 管理 UI 相关数据:

kotlin 复制代码
// ViewModel
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<String>() // 内部可变的 MutableLiveData
    val data: LiveData<String> get() = _data      // 对外暴露为 LiveData,避免外部直接写入

    fun updateData(newValue: String) {
        _data.value = newValue // 更新 LiveData 值
    }
}

// Fragment 或 Activity
class MyFragment : Fragment() {
    private val viewModel: MyViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // 观察 LiveData
        viewModel.data.observe(viewLifecycleOwner) { updatedData ->
            // 用新数据更新 UI
            textView.text = updatedData
        }
    }
}

上例中,MyViewModel 保存数据,Fragment 通过观察 LiveDataupdateData() 被调用时自动刷新 UI。

上述代码中,你会看到 MutableLiveDataLiveData,这两者的差别其实很简单:

  • MutableLiveData: 通常可用 setValue() / postValue() 修改;常见实践是在 ViewModel 中内部持有可变版本,并对外用只读 LiveData 暴露,避免被外部直接改写。
  • LiveData(只读暴露): 更强调封装与避免外部非预期写入。

类似 MutableListList 的差别。

面试一问

LiveData 如何体现生命周期感知?与 RxJavaEventBus 相比,优势在哪里?

setValue()postValue()LiveData 中有什么区别?各自更适用于什么时候?

LiveData 有哪些局限?当多个 UI 事件(导航、Toast 等)需要被观察,又不希望在配置变化后被重复触发时,你通常会怎么处理?

使用场景

  1. UI 状态管理: 把网络/数据库等来源数据放入 LiveData,与 UI 绑定,数据变化时界面同步更新。
  2. 观察者模式落地: 作为发布/订阅里的一方,在值变化时通知订阅方;适合更动态的 UI/交互数据。
  3. One-time events(一次性事件): 例如 Toast、跨页面导航等;这类场景也常见 SingleLiveEvent 等变体/封装,以避免重复触发等问题。

额外知识

LiveData 常会被拿来与 Kotlin 的 StateFlowSharedFlow 对比,不少开发者会迁移到 Flow 或正考虑迁移。

有人会觉得 StateFlowLiveData "过时了",这句话从趋势上看有一定道理。不过有一点你需要搞清楚,Flow 是平台无关的,本身并不了解 Android 生命周期,因此收集与取消需要你在生命周期边界下显式处理。

没有可靠的生命周期感知式收集,Flow 可能在 UI 不活跃时仍继续收集,从而增加资源浪费甚至泄漏等风险。相比之下,LiveData 会绑定到 LifecycleOwner 并更自动地处理取消订阅,因此在不少场景下仍然更"省心"、也更实际。

另有一种说法是"从 LiveData 迁到 Flow 就能拿掉 Android 依赖",这句话不一定站得住脚:不是所有人都有跨平台的需求。

在实践中,LiveData 往往只出现在 ViewModel 边界,是否要把网络/数据/领域层都换成更 JVM-only 的模块化架构,以及收益是否足够明确,也因人而异。

所以不必盲目追热点,当你真的需要 Flow,并且确实能拿到 Flow 的收益(更复杂数据转换、更丰富的响应式流处理等)时,再引入会更合适。

总结

LiveData 让构建 响应式 + 更生命周期安全 的 UI 状态更简单:用一种更"生命周期有感知"的方式观察数据变化,减少样板并降低在错误生命周期更新等带来的风险。它也因此成为现代 Android 开发、尤其是 MVVM 中的常用组件之一。

进阶:setValue 与 postValue

LiveData 中,两者都用于更新所持有的值,但它们的线程语义与使用场景不同。

setValue

setValue() 用于同步更新值,并且必须在主线程调用。当你希望立刻让观察者在本帧/同步路径里收到更新,它更合适。

kotlin 复制代码
val liveData = MutableLiveData<String>()

fun updateOnMainThread() {
    liveData.setValue("Updated Value") // 必须在主线程调用
}

这适合已经在主线程触发的更新(例如直接 UI 事件、生命周期回调内的同步更新等)。在后台线程调用 setValue() 通常会直接出错。

postValue

postValue() 用于在后台线程投递一个会在主线程生效的更新,从设计上更适合后台线程,避免在后台线程里手动切回主线程。

postValue() 不阻塞当前调用线程,而是把值投递到主线程去应用。

kotlin 复制代码
val liveData = MutableLiveData<String>()

fun updateInBackground() {
    Thread {
        liveData.postValue("Updated Value") // 可在任意线程调用
    }.start()
}

在涉及网络、数据库等后台工作时,postValue() 很常用,因为它不需要你显式再切主线程去更新 LiveData

如果查看 postValue() 的内部实现,它通常会通过执行器/调度,把"在主线程上应用新值"这件事投递出去:

java 复制代码
protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

它会通过 mDataLock 同步、更新 mPendingData,并判断是否需要向主线程投递任务;也解释了为何短时间内多次 postValue() 可能只让观察者看到"最后那次"的合并行为(以内部实现为准)。

很多开发者可能会使用 postValue() 去投递序列。现在你知道了吧,这个做法其实有隐患。

如果下面这段运行在主线程:

kotlin 复制代码
liveData.postValue("a")
liveData.setValue("b")

"b" 会立刻被设置,但之后主线程若仍处理到 postValue("a") 投递来的任务,最终值可能被覆盖为 "a"。原因是 postValue() 的异步调度与 setValue() 的同步更新交织。

关键差异

方面 setValue postValue
线程 必须在主线程调用 可在任意线程调用
同步/异步 同步立即更新 异步投递到主线程再更新
使用场景 主线程上发起的更新(交互、生命周期等) 后台线程/异步工作完成后要更新到 UI 侧时
观察者通知 同帧/同步路径上更快触发 主线程处理投递任务后触发(与帧调度相关)

用法建议

  • setValue() 更新已在主线程产生(用户交互、生命周期内同步结果等)。
  • postValue() 在后台线程拿到结果后,需要把值安全送到主线程侧(网络、DB、长任务等)。

总之。

setValue()postValue() 都用于更新 LiveData 值,但线程模型不同:前者主线程同步,后者可跨线程并以投递方式落到主线程。选对了才能既线程安全,又让数据更新对 UI 更平滑。

ViewModel 是什么

Jetpack ViewModel 是 Android 架构组件中的关键能力之一,用于在与生命周期更匹配的边界内保存与管理 UI 相关数据。

这个架构组件有多重要?AndroidX 甚至在跨平台方向上也保留了 ViewModel

它把 UI 侧逻辑与业务侧逻辑更清晰地分层,并尽量让数据在配置变化(如屏幕旋转)中得以保留,从而帮助应用更稳、更易于维护。

ViewModel 的核心目标之一,是在配置变化期间保留与 UI 强相关的数据:例如用户旋转设备导致 Activity / Fragment 重建时,ViewModel 仍可能保留关键状态,避免你重复拉取/重复计算。

kotlin 复制代码
data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // 暴露屏幕 UI 状态
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // 处理业务逻辑
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

这个例子说明:即使 Activity 因配置变化重建,状态仍可能由 ViewModel 维持。

面试一问

ViewModel 如何在配置变化中"持久化"UI 相关数据?与 onSaveInstanceState() 等保存方式的核心差异是什么?

ViewModelStoreOwner 的用途是什么?同一 Activity 下多个 Fragment 间如何共享 ViewModel

ViewModel 内用 StateFlowLiveData 管理 UI 状态,各自优势与可能踩坑分别是什么?

特性

  1. Lifecycle Awareness(生命周期作用域): ViewModel 通常与某个 Activity / Fragment 的作用域绑定,并在对应 UI 组件不再需要时被清理(如用户离开页面)。
  2. Persistence Across Configuration Changes(跨配置变化保留状态): 与会在配置变化中销毁/重建的 Activity / Fragment 不同,ViewModel 往往更能保留其内部状态,减少丢数据/重复重取。
  3. Separation of Concerns(关注点分离): 把与 UI 展示强相关的状态/逻辑与业务逻辑分层,让 UI 侧观察/订阅 ViewModel 输出,更利于用响应式思路组织代码。

我们来看一下它的创建方式和使用示例。

ComponentActivity 上,可以用 Kotlin 的 by viewModels() 委托来创建 ViewModel(来自 activity-ktx 等 Jetpack 扩展能力),让代码更干净:

kotlin 复制代码
class DiceRollActivity : AppCompatActivity() {

    // 使用 `by viewModels()` Kotlin 属性委托
    // 来自 activity-ktx
    private val viewModel: DiceRollViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 系统第一次调用该 Activity 的 onCreate 时创建 ViewModel。
        // 配置变化导致 Activity 重建时,会拿到同一份 DiceRollViewModel 实例。

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // 更新 UI
                }
            }
        }
    }
}

ViewModel 实例会绑定在 ViewModelStoreOwner 上:这个 owner 可以是 ActivityFragmentNavigation 图、图中某个 destination,甚至是自定义 owner。Jetpack 也提供了很丰富的"作用域化取 ViewModel"的方式。

图解 ViewModel

该图列出了 Jetpack 中与 ViewModel 相关的 API、返回实例的作用域,以及使用示例。并提示可将 ViewModel 挂到 ViewModelStoreOwner 上,owner 可以是 ActivityFragmentNavigation 图/destination 或自定义 owner。

在 "Get an instance in Jetpack Compose" 中(lifecycle-viewmodel-compose)包含 viewModel()(取最近 ViewModelStoreOwner)与 viewModel(vmStoreOwner)(指定任意 ViewModelStoreOwner,示例含 Navigation backStackEntry / 父级图路由等)等要点;在 "Compose + Navigation + Hilt "(hilt-navigation-compose)中强调与 Hilt + Navigation 同用时可用 hiltViewModel()(并配套示例)。

在 "Core ViewModel API"(lifecycle-viewmodel-ktx)展示 ViewModelProvider 等。

在 "Get an instance in an Activity"(activity-ktx)展示 by viewModels()

在 "Get an instance in a Fragment"(fragment-ktx)展示 by viewModels()、带 ownerProducer 的变体、以及 by activityViewModels();在 "Fragment + Navigation"(navigation-fragment)展示 by navGraphViewModels;在 "Fragment + Navigation + Hilt"(hilt-navigation-fragment)展示 by hiltNavGraphViewModels,并说明 Hilt + Navigation 场景下应优先使用这些 API,而非简单 navGraphViewModels;不挂在 NavGraph 的实例则仍可用 viewModels() / activityViewModels()

该图内容较多,但是并不复杂,各位不必一次性看完。

总结

Jetpack ViewModel 用于管理 UI 相关数据,并尽量在配置变化中连续保留(例如屏幕旋转时不必因重建就把状态全丢回初始)。它是生命周期有感知的一类状态容器/协调者,也更常与 MVVM 等架构模式一起使用:通过保留状态与更清晰的边界,让状态管理与开发体验都更顺。

进阶:ViewModel 的生命周期

ViewModel 的生命周期与其 ViewModelStoreOwner 关联;这个 owner 可以是 ActivityFragment,或其他与生命周期相关的承载者。

只要 ViewModelStoreOwner 仍在作用域中,ViewModel 就仍然存在,让数据与状态在配置变化(如屏幕旋转)中得以保留。

这种设计让 ViewModel 成为管理 UI 相关数据、并在此类变化中保持状态的重要组件。

例如,对 Activity 来说,ViewModel 往往持续到该 Activity 被真正结束并清理对应的 ViewModelStore。于是数据与状态也能够在配置变化期间被保留下来。

上图概览了 ViewModelActivity 生命周期中的存续区间:它展示 ViewModel 如何随 Activity 的生命周期事件被创建并保留,即便 Activity 被暂时销毁后重建,实例仍可能延续。虽然示例是 Activity,但同样适用于 Fragment 以及作为 ViewModelStoreOwner 的其它承载者。

从图中看,左侧是 Activity 生命周期与关键节点:从 Activity created 开始,经 onCreateonStartonResumeActivity rotated 时经历 onPauseonStoponDestroy,随后重建的 Activity 继续 onCreateonStartonResume;在更靠下的位置有 finish() 触发,并最终 onPauseonStoponDestroy 以进入 Finished。右侧/中部是贯穿的 ViewModel Scope 区域,覆盖初次创建、旋转与重建;清理点以 onCleared() 为标志。

ViewModelStoreOwner 第一次被创建时,ViewModel 会被初始化。只要 owner 仍在内存中,通常会保留同一份 ViewModel 实例。

若发生配置变化(如旋转设备),owner 可能重建,但已存在的 ViewModel 往往被复用,避免重新加载或重新初始化数据。这种复用通常带来更好的性能与更顺滑的体验。

最后,当 ViewModelStoreOwner 被永久销毁时(例如 Activityfinish、或 Fragment 从父级中移除且预期不再回来),ViewModel 会被清理。

此时会调用其 onCleared(),用于取消协程、释放长期持有的资源等,以降低内存泄漏风险。ViewModel 的生命周期因此有助于在状态续存资源管理之间取得平衡。

进阶:ViewModel 为什么会被保留

在 Android 中,Jetpack ViewModel 被设计为能挺过屏幕旋转、系统语言/区域变更等配置变化。

为某个 UI 组件(如 Activity/Fragment)创建 ViewModel 时,它会绑定到该组件提供的 ViewModelStoreOwner:对 Activity 来说,这个 owner 通常由 ComponentActivity 表示;对 Fragment 来说则是 Fragment 自身。

更底层的关键是 ViewModelStore:它会在配置变化中被保留,使 ViewModel 在界面重建时仍有机会保留其内部数据,而不必每次从零开始。

在 Jetpack 实现里,androidx.activity.ComponentActivityandroidx.fragment.app.Fragment 都实现了 ViewModelStoreOwner:让 Activity/Fragment 拥有自己的 ViewModelStore,以在配置变化中持续持有 ViewModel 实例。ViewModelStore 内部以 Map(key 为 String)管理实例,如:

kotlin 复制代码
public open class ViewModelStore {

    private val map = mutableMapOf<String, ViewModel>()

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public fun put(key: String, viewModel: ViewModel) {
        val oldViewModel = map.put(key, viewModel)
        oldViewModel?.clear()
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public operator fun get(key: String): ViewModel? {
        return map[key]
    }

    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    public fun keys(): Set<String> {
        return HashSet(map.keys)
    }

    public fun clear() {
        for (vm in map.values) {
            vm.clear()
        }
        map.clear()
    }
}

ComponentActivity 还会观察自身生命周期:在 ON_DESTROY 时,会清理 Context 相关帮助器,并且若不是配置变化导致的销毁,会清理 ViewModelStore,以释放 ViewModel 实例:

java 复制代码
getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            // 清理可用 context
            mContextAwareHelper.clearAvailableContext();
            // 并清理 ViewModelStore
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
            mReportFullyDrawnExecutor.activityDestroyed();
        }
    }
});

总之。

ViewModel 之所以能在配置变化中"活下来",与 ViewModelStore 的持有方式密切相关:由绑定生命周期的 Android 组件管理。

Compose Navigation 中,ViewModelStore 也会与导航路由/作用域产生关联,以按导航作用域保留状态。你当然也能手动管 ViewModelStore,但配置变化下正确"找回/复用"往往更绕,一般不建议自研乱改。

进阶:Jetpack 与 Microsoft

根据 Android 官方文档的表述,Jetpack 的 ViewModel 是一个生命周期有感知的组件,适合作为 业务逻辑 / 屏级状态 的承载体:对 UI 暴露状态,并封装与界面展示强相关的业务逻辑。

它的强项之一,是把状态缓存/延续在配置变化(如旋转、Activity 重建等)中,让 UI 不必无意义地重复取数/重复计算。总体上,它更"Android 化"、更围绕生命周期来组织状态。

MVVM(Model-View-ViewModel) 最初由 Microsoft 系统阐述时,其中的 ViewModel 更偏"ViewModel 的桥梁":实现可绑定到 View 的属性/命令,并通过通知机制把状态变化告知 View

它要协调 ViewModel 的访问,并尽量把业务逻辑从直接操控 UI 中抽离。与 Jetpack ViewModel 相比,MVVMViewModel 更强调数据绑定与被动 UI 这套机制,而不是 Android 上"过配置不丢"的那条线。

从图中可以看到,三个框为 ViewViewModelModelViewViewModel 的实线箭头标为 Data Binding and CommandsViewModelModel 的实线箭头标为 ViewModel updates the modelModelViewModelViewModelView 的虚线箭头标为 Send notifications

在根本层面,二者同名但范式不同:Jetpack ViewModel 更强调 Android 上的生命周期有感知状态 与跨配置续存;MVVM 模式里的 ViewModel 更强调 绑定/命令/解耦 以让 View 更被动。

名字相似不代表"只用它就等于 MVVM "。要更接近 MVVM 的原始目标,你往往还需要额外的数据绑定/命令体系,让 UI 能被动地响应 ViewModel 提供的数据,并把业务逻辑与 UI 层更清晰地分层,从而提高可测性与可维护性。

简单来说,ViewModel + Flow/LiveData 可以承担 MVVMViewModel 的一部分职责,但二者不能简单画等号。

总的来说。

二者范围与落点不同:Jetpack ViewModel 更服务 Android 上"UI 相关状态在生命周期/配置变化下如何更稳地组织";MVVMViewModel 更服务"Model-View-ViewModel 之间如何用绑定把 UI 做声明式/被动化"。

在 Jetpack Compose 中,更常见的是直接从 ViewModel 观察流/状态来驱动 UI;在传统 XML 上,要更贴近经典 MVVM 的被动 UI,常还需要 DataBinding 等能力配合。

对于开发者来讲,你仍应按架构与 UI 技术栈选择更合适的组合。

相关推荐
用户86022504674722 小时前
Jetpack ViewModel 入门与实践
android
随遇丿而安2 小时前
第3周:按钮这件小事,真正麻烦的是“点完以后”
android
峥嵘life4 小时前
五一南昌第三天游玩记录:梅景寻芳,母校忆旧,摩天轮揽夜
android
UXbot4 小时前
AI画原型工具如何帮非设计师快速生成UI界面
前端·vue.js·ui·kotlin·swift·原型模式·web app
qq_452396235 小时前
第三篇:《JMeter断言:验证接口响应正确性》
android·jmeter
aqi005 小时前
一文速览 HarmonyOS 6.0.1 引入的十个新特性
android·华为·harmonyos·鸿蒙·harmony
橙子199110167 小时前
Android 第三方框架 相关
android
赏金术士7 小时前
JetPack Compose 弹窗、菜单、交互组件(五)
android·kotlin·交互·android jetpack·compose
小书房7 小时前
Kotlin的协程
kotlin·高并发·协程·异步·虚拟线程·coroutinescope