首页 / Android 入门教程 / ViewModel

Android 入门教程

ViewModel

本教程共 100 篇 · 第 78 篇 · 更新于 2026-07-28 · 约 8 分钟阅读

AndroidAndroid 入门教程ViewModelJetpackStateFlowCompose架构

78. ViewModel

本节目标:理解 ViewModel 的定位和生命周期,学会定义 ViewModel、用 StateFlow 暴露状态、绑定协程作用域、在 Compose 中获取并观察 ViewModel。

上一章架构指南里 ViewModel 是界面层的状态容器,这章讲怎么写。

ViewModel 解决什么问题

打个比方,Activity 像个临时工,系统让你下岗你就下岗(旋转屏幕、内存不足)。临时工手里的话单(数据)一交接就丢。

ViewModel 像正式员工,只要部门还在(屏幕没退出返回栈),不管底下临时工怎么换,他手里的活儿和数据都不丢。

具体来说,ViewModel 解决两个核心问题:

  1. 配置变化时保留状态:旋转屏幕、切换语言,Activity 重建但 ViewModel 不重建。
  2. 托管业务逻辑:UI 只管显示和发事件,业务逻辑放 ViewModel,可测试、可复用。

第一个 ViewModel

最简单的 ViewModel:

class CounterViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count.asStateFlow()

    fun increment() {
        _count.value = _count.value + 1
    }

    fun decrement() {
        _count.value = _count.value - 1
    }
}

要点:

  • 继承 ViewModel
  • 内部用 MutableStateFlow 持有可变状态,对外暴露不可变的 StateFlow
  • 业务逻辑就是改 _count 的值。
Note

对外暴露不可变类型是关键。如果直接暴露 MutableStateFlow,外部能随便改,状态就乱了。asStateFlow() 转成只读。

在 Compose 里用

Compose 用 viewModel() 函数获取 ViewModel 实例:

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    val count by viewModel.count.collectAsStateWithLifecycle()

    Column {
        Text("当前计数:$count")
        Button(onClick = { viewModel.increment() }) { Text("+1") }
        Button(onClick = { viewModel.decrement() }) { Text("-1") }
    }
}

要点:

  • viewModel() 默认按作用域缓存实例,同一个 ViewModelStoreOwner 下获取的是同一个实例。
  • collectAsStateWithLifecycle() 在生命周期非活跃时自动停止收集,省电。
  • Composable 重组时 ViewModel 不重建,状态保留。
Tip

collectAsStateWithLifecycle 来自 lifecycle-runtime-compose 库,比 collectAsState 更省电。记得加依赖:implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.x")

在 Activity/Fragment 里用

View 体系下用 viewModels() 委托:

class MainActivity : AppCompatActivity() {
    private val viewModel: CounterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 观察状态...
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.count.collect { count ->
                    countText.text = count.toString()
                }
            }
        }
    }
}

Fragment 里要用 viewLifecycleOwner 而不是 this,避免 Fragment 视图销毁后还在收集。

ViewModel 生命周期

ViewModel 的生命周期比 Activity 长。从首次获取到 Activity finish 之间都存活:

  • 旋转屏幕:Activity 重建,ViewModel 不变。
  • 切换语言:同上。
  • 后台返回:Activity 没销毁,ViewModel 不变。
  • Activity finish:ViewModel 销毁,调用 onCleared()
  • 系统杀进程:ViewModel 也没了,需要 SavedStateHandle 持久化。
Activity 创建 -> 获取 ViewModel
        |
   (旋转屏幕:Activity 重建,ViewModel 保留)
        |
   (使用中...)
        |
Activity finish -> ViewModel.onCleared()
Warning

ViewModel 不应该在构造函数里持有 ContextViewResources 这类和 UI 绑定的对象。ViewModel 比 Activity 活得长,持有会泄漏。要 Context 就用 AndroidViewModel(持有 Application Context)。

AndroidViewModel

需要 Application Context 时用 AndroidViewModel

class MyViewModel(app: Application) : AndroidViewModel(app) {
    fun doSomething() {
        val prefs = getApplication<Application>().getSharedPreferences("x", 0)
        // ...
    }
}

但更推荐的做法是把 Context 相关的逻辑放到 Repository,ViewModel 只依赖 Repository,便于测试。

ViewModelFactory

ViewModel 有构造参数时,要用 Factory 告诉系统怎么创建:

class UserViewModel(private val repo: UserRepository) : ViewModel() {
    // ...
}

class UserViewModelFactory(private val repo: UserRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return UserViewModel(repo) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

使用:

val viewModel by viewModels<UserViewModel> {
    UserViewModelFactory(UserRepository(/* ... */))
}
Note

Factory 写法啰嗦,现代项目一般用 Hilt 自动注入,下一章后面会讲。手写 Factory 适合小项目或者学习阶段。

Compose 里传参数

Compose 里带参数的 ViewModel 用 CreationExtras:

class DetailViewModel(private val itemId: String) : ViewModel() {
    // ...
}

// Composable 里
@Composable
fun DetailScreen(itemId: String) {
    val viewModel: DetailViewModel = viewModel(
        factory = viewModelFactory {
            initializer {
                DetailViewModel(itemId)
            }
        }
    )
}

或者用 Navigation Compose 的 navArgument,把参数从路由取出来传给 ViewModel。

协程作用域

ViewModel 自带 viewModelScope,是个绑定到 ViewModel 生命周期的协程作用域:

class UserViewModel(private val repo: UserRepository) : ViewModel() {
    private val _uiState = MutableStateFlow<UserUiState>(Loading)
    val uiState = _uiState.asStateFlow()

    fun loadUser(id: String) {
        viewModelScope.launch {
            _uiState.value = Loading
            try {
                val user = repo.getUser(id)
                _uiState.value = Success(user)
            } catch (e: Exception) {
                _uiState.value = Error(e)
            }
        }
    }
}

sealed interface UserUiState {
    object Loading : UserUiState
    data class Success(val user: User) : UserUiState
    data class Error(val exception: Throwable) : UserUiState
}

ViewModel 一销毁,viewModelScope 自动取消,所有协程跟着取消,不会泄漏。

Tip

viewModelScope 用的是 Dispatchers.Main.immediate,主线程调度。网络、数据库的 suspend 函数自己会切 IO,不用手动 withContext

SavedStateHandle

进程被系统杀掉后,ViewModel 也没了。要保留状态用 SavedStateHandle

class MyViewModel(private val state: SavedStateHandle) : ViewModel() {
    var query: String
        get() = state.get<String>("query") ?: ""
        set(value) { state["query"] = value }
}

SavedStateHandle 数据存到 Bundle,进程重启后恢复。适合存搜索框文本、当前选中的 tab 等小量数据。

Warning

SavedStateHandle 只能存可序列化的简单类型(String、Int、Boolean、Parcelable 等)。大数据用 Room 持久化,别塞 SavedStateHandle。

onCleared 清理资源

ViewModel 销毁前会调 onCleared(),用来清理资源:

class PlayerViewModel : ViewModel() {
    private var player: MediaPlayer? = null

    init {
        player = MediaPlayer()
    }

    override fun onCleared() {
        super.onCleared()
        player?.release()
        player = null
    }
}

状态设计

UI 状态怎么设计很关键。推荐用密封类表示多种状态:

sealed interface UiState<out T> {
    object Loading : UiState<Nothing>
    data class Success<T>(val data: T) : UiState<T>
    data class Error(val message: String) : UiState<Nothing>
}

UI 层用 when 处理:

when (val state = uiState) {
    is Loading -> CircularProgressIndicator()
    is Success -> ShowContent(state.data)
    is Error -> ShowError(state.message)
}
Note

状态类要不可变(data class 全 val)。可变状态导致重组不可预测,调试噩梦。

常见坑

  1. ViewModel 持有 View/Activity 引用:内存泄漏。用 AndroidViewModel 或者拆到 Repository。
  2. Repository 返回 LiveData 给 ViewModel:LiveData 应该只在 UI 层。Repository 用 Flow。
  3. 直接暴露 MutableStateFlow:外部乱改。用 asStateFlow()
  4. 在 ViewModel 里做主线程阻塞操作:UI 卡。用协程切 IO。
  5. 忘用 viewModelScope:用 GlobalScope 或者 CoroutineScope() 启动协程,无法管理,泄漏。
  6. Fragment 里用 this 观察 LiveData:Fragment 视图销毁后还在收,崩。用 viewLifecycleOwner

小结

ViewModel 是界面层状态容器,配置变化时保留状态,托管业务逻辑。用 MutableStateFlow + asStateFlow() 暴露不可变状态,viewModelScope 启动协程,SavedStateHandle 持久化小数据,onCleared() 清理资源。Compose 里用 viewModel() 获取,collectAsStateWithLifecycle() 观察。

下一章讲 LiveData,理解它和 StateFlow 的区别。