2026 视角:深入解析 Android 架构组件中的 ViewModel 与现代 UI 模式

在我们构建 Android 应用的漫长旅程中,你是否曾因屏幕旋转导致应用崩溃或数据丢失而感到头疼?或者,随着业务逻辑的复杂化,你是否发现 Activity 和 Fragment 中的代码变得臃肿不堪,难以维护?如果我们希望构建健壮、整洁且易于维护的应用,就必须面对数据管理与 UI 生命周期解耦的问题。在这篇文章中,我们将深入探讨 Android Jetpack 架构组件中的核心成员——ViewModel,并结合 2026 年的开发趋势,看看它如何优雅地解决数据保留问题,以及它如何与现代 UI 模式(如 Jetpack Compose 和 MVI)协同工作,成为连接 UI(视图层)与数据层的最佳桥梁。

为什么我们需要 ViewModel?

首先,让我们回顾一下 Android 开发中的一个经典痛点。在默认情况下,当设备发生配置更改(例如屏幕旋转、键盘可用性变化或语言切换)时,当前的 Activity 或 Fragment 会被完全销毁并重新创建。这意味着,存储在 UI 控件中的临时数据,以及 Activity 类成员变量中的数据,都会随着实例的销毁而丢失。

在过去,为了在配置更改后保留这些数据,我们可能不得不重写 onSaveInstanceState,将数据序列化存入 Bundle。然而,这种方法不仅繁琐,而且 Bundle 受限于序列化能力和大小限制(通常仅建议存储少量简单类型),并不适合存储复杂的业务对象或网络请求结果。此外,如果我们直接在 Activity 中进行网络请求,当屏幕旋转时,正在进行的请求可能会因为 Activity 的销毁而导致内存泄漏或数据重复加载。

为了避免这些问题并分离关注点,我们建议不要直接在 Activity 或 Fragment 中处理数据逻辑,而是使用 ViewModel。它是我们应对“配置更改”这一顽固敌手的最有力武器。

ViewModel 的核心职责与生命周期

ViewModel 类专门设计用于以生命周期感知的方式存储和管理与 UI 相关的数据。它的核心目的非常简单:持有并管理与 UI 相关的数据。更重要的是,它的生命周期长于 UI 组件(如 Activity)。当屏幕旋转时,ViewModel 对象不会被销毁,系统会保留该实例,并将其关联到新创建的 Activity 实例中。这使得数据能够在配置更改期间无缝存活,而无需任何额外的“保存”或“恢复”逻辑。

!ViewModel 生命周期示意图

上图清晰地展示了 ViewModel 的生命周期。它仅在 Activity 真正 finish(彻底退出)或用户按下返回键时才会被清除,而在屏幕旋转等配置重建过程中依然保持存活。这意味着,我们可以在 ViewModel 中安全地缓存数据,而不必担心因 UI 重建而丢失状态。

实战演练:从传统到现代的进化

为了让你深刻体会到 ViewModel 的价值,我们将采用对比的方式。首先,我们将编写一个不使用 ViewModel 的计数器应用,看看它的问题在哪里;然后,我们将对其进行重构,引入 ViewModel 来解决问题;最后,我们将展示 2026 年我们如何使用 Kotlin Flow 和协程来构建响应式的 ViewModel。

#### 场景设置

我们构建一个简单的应用,包含一个显示数字的 TextView 和一个用于增加数字的 Button。我们的目标是:当用户点击按钮增加计数,然后旋转屏幕时,屏幕上的数字不应该归零。

#### 步骤 1:配置项目依赖

在开始编写代码之前,我们需要确保项目已经引入了必要的库。在现代 Android 开发(2026 标准)中,我们主要使用 Kotlin 和 KTX 扩展库。请打开你的 build.gradle (Module: app) 文件。

// build.gradle (Module: app)

plugins {
    id ‘com.android.application‘
    id ‘org.jetbrains.kotlin.android‘
}

def lifecycle_version = "2.8.0" // 2026年推荐使用的稳定版本

dependencies {
    // ViewModel 核心库
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    
    // LiveData(虽然 Flow 正在崛起,但 LiveData 在 View 系统中依然常用)
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
    
    // Kotlin Coroutines - 现代异步处理的核心
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0"
    
    // 核心 KTX 扩展库
    implementation "androidx.core:core-ktx:1.15.0"
}

> 注意:在早期的教程中,你可能会看到 kotlin-android-extensions 插件。但该插件早已废弃。在 2026 年,我们强烈推荐使用 ViewBinding(对于 XML 布局)或 Jetpack Compose(声明式 UI)。为了简化本文的代码量并保持聚焦于 ViewModel,下文我们将假设你正在使用 ViewBinding 来访问视图。

#### 反面教材:未使用 ViewModel 的混乱

让我们先来看看如果不使用 ViewModel,事情会变得多么糟糕。在 MainActivity.kt 中,我们尝试直接在 Activity 中维护计数数据。

kotlinn// 这里的代码示例展示了反模式
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.myviewmodelapp.databinding.ActivityMainBinding // 假设使用了 ViewBinding

class MainActivity : AppCompatActivity() {

// 问题出在这里:我们将数据直接保存在 Activity 中
// 当屏幕旋转时,Activity 实例被销毁,count 也会被重置为 0
private var count: Int = 0
private lateinit var binding: ActivityMainBinding

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

binding.textView.text = count.toString()

binding.button.setOnClickListener {
count++
binding.textView.text = count.toString()
}
}
}
CODEBLOCK_bc3a7986kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

// 我们的 ViewModel 需要继承 ViewModel 类
class MainActivityViewModel : ViewModel() {

// 【现代写法】使用 StateFlow 来管理状态
// StateFlow 是 Kotlin 协程的一部分,非常适合处理 UI 状态流
private val _count = MutableStateFlow(0)

// 暴露不可变的 StateFlow 给 UI 层,确保数据单向流动
val count = _count.asStateFlow()

// 【现代写法】使用挂起函数模拟复杂的业务逻辑
// 在实际项目中,这里可能是数据库操作或网络请求
fun incrementCount() {
_count.value++ // 简单的计数可以直接在主线程更新
}

// 模拟一个耗时的异步操作,展示 ViewModel 如何处理后台任务
fun heavyOperation() {
viewModelScope.launch(Dispatchers.IO) {
// 模拟网络延迟
Thread.sleep(1000)
_count.value += 100
}
}
}
CODEBLOCK_ff2da1abkotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.example.myviewmodelapp.databinding.ActivityMainBinding
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

private lateinit var viewModel: MainActivityViewModel
private lateinit var binding: ActivityMainBinding

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

// 关键步骤:通过 ViewModelProvider 获取 ViewModel 实例
// 这行代码告诉系统:为这个 Activity(作用域)获取或创建一个 MainActivityViewModel
viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]

// 【现代写法】使用 repeatOnLifecycle 收集 Flow
// 这是 2026 年推荐的最安全、最高效的方式在 UI 层观察数据流
lifecycleScope.launch {
// 当生命周期至少处于 STARTED 状态时开始收集
repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.STARTED) {
viewModel.count.collect { count ->
// 更新 UI
binding.textView.text = count.toString()
}
}
}

binding.button.setOnClickListener {
viewModel.incrementCount()
}

binding.heavyButton.setOnClickListener {
viewModel.heavyOperation()
}
}
}
CODEBLOCK_0706ef9ekotlin
// 在 Fragment 中获取 Activity 作用域的 ViewModel
val viewModel: SharedViewModel by activityViewModels()
CODEBLOCK_9d6f79f1kotlin
// 在 Composable 函数中
@Composable
fun CounterScreen(viewModel: MainActivityViewModel = viewModel()) {
// collectAsState() 将 Flow 转换为 Compose 需要的 State
val count by viewModel.count.collectAsState()

Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "Count: $count", style = MaterialTheme.typography.h3)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = { viewModel.incrementCount() }) {
Text("Increment")
}
}
}
CODEBLOCK_bafa86bbkotlin
package com.example.myviewmodelapp

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class MainActivityViewModel : ViewModel() {

// 使用 MutableStateFlow 管理内部状态
private val _count = MutableStateFlow(0)

// 暴露不可变的 StateFlow
val count = _count.asStateFlow()

fun increment() {
_count.value++
}

// 模拟复杂的异步逻辑
fun loadData() {
viewModelScope.launch {
// 模拟异步加载
_count.value = 100
}
}
}
CODEBLOCK_32308675kotlin
package com.example.myviewmodelapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.example.myviewmodelapp.databinding.ActivityMainBinding
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {

private lateinit var viewModel: MainActivityViewModel
private lateinit var binding: ActivityMainBinding

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

viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]

// 使用 lifecycleScope 和 repeatOnLifecycle 安全地收集数据流
lifecycleScope.launch {
repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.STARTED) {
viewModel.count.collect { countValue ->
binding.textView.text = countValue.toString()
}
}
}

binding.button.setOnClickListener {
viewModel.increment()
}
}
}

通过这种方式,我们不仅保留了数据,还实现了逻辑与 UI 的完全解耦,并且利用 Kotlin 的并发特性保证了代码的高效与安全。这就是现代 Android 开发的标准姿势。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/36326.html
点赞
0.00 平均评分 (0% 分数) - 0