Android 中的MVVM 和 ViewModel

一、MVVM 是什么?

1. MVVM 分别代表什么?

MVVM 是一种架构模式:

角色 作用
Model 数据层,例如数据库、网络、Repository
View UI 层,例如 Activity、Fragment、Compose
ViewModel 连接 View 和 Model,负责 UI 状态管理和业务逻辑

2. MVVM 的核心思想是什么?

核心思想是:

View 只负责展示和用户交互,ViewModel 负责处理 UI 逻辑和状态,Model 负责数据来源。

这样可以降低耦合,提高可测试性和可维护性。


二、Android 中 MVVM 的典型结构

ui/
 ├── MainActivity.kt
 ├── MainViewModel.kt

data/
 ├── UserRepository.kt
 ├── UserApi.kt
 ├── UserDao.kt

model/
 ├── User.kt

三、ViewModel 是什么?

1. ViewModel 的作用是什么?

ViewModel 是 Android Jetpack 提供的组件,主要作用:

  1. 保存和管理 UI 相关数据
  2. 屏蔽配置变化,例如横竖屏切换
  3. 分离 UI 逻辑和 Activity / Fragment
  4. 配合 LiveData、StateFlow 暴露状态
  5. onCleared() 中释放资源

2. ViewModel 为什么能在横竖屏切换后保留数据?

因为 ViewModel 的生命周期比 Activity / Fragment 的普通实例更长。

当 Activity 因配置变化重建时,系统会保留 ViewModelStore,新的 Activity 会重新获取之前的 ViewModel 实例。


四、ViewModel 基础代码

1. data class

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

2. Repository

class UserRepository {

    suspend fun getUser(): User {
        // 模拟网络请求
        kotlinx.coroutines.delay(1000)
        return User(
            id = 1,
            name = "Tom",
            age = 20
        )
    }
}

3. ViewModel 使用 StateFlow

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

class UserViewModel(
    private val repository: UserRepository = UserRepository()
) : ViewModel() {

    private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UserUiState.Loading

            try {
                val user = repository.getUser()
                _uiState.value = UserUiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UserUiState.Error(e.message ?: "未知错误")
            }
        }
    }
}

4. UI 状态 sealed class

sealed class UserUiState {
    object Loading : UserUiState()

    data class Success(
        val user: User
    ) : UserUiState()

    data class Error(
        val message: String
    ) : UserUiState()
}

5. Fragment 中使用 ViewModel

import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch

class UserFragment : Fragment(R.layout.fragment_user) {

    private val viewModel: UserViewModel by viewModels()

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

        observeData()

        viewModel.loadUser()
    }

    private fun observeData() {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is UserUiState.Loading -> {
                            // showLoading()
                        }

                        is UserUiState.Success -> {
                            // showUser(state.user)
                        }

                        is UserUiState.Error -> {
                            // showError(state.message)
                        }
                    }
                }
            }
        }
    }
}

五、常见面试题


1. ViewModel 可以持有 View 吗?

不建议。

ViewModel 不应该持有 Activity、Fragment、View、Context 等 UI 对象引用。

错误示例:

class MainViewModel : ViewModel() {

    lateinit var activity: MainActivity
}

这样可能导致内存泄漏。

正确做法:

class MainViewModel : ViewModel() {

    private val _message = MutableStateFlow("")
    val message: StateFlow<String> = _message

    fun onButtonClick() {
        _message.value = "按钮被点击"
    }
}

View 层观察状态并更新 UI。


2. ViewModel 中可以使用 Context 吗?

普通 ViewModel 不建议持有 Context。

如果确实需要 Application Context,可以使用 AndroidViewModel

import android.app.Application
import androidx.lifecycle.AndroidViewModel

class MyViewModel(
    application: Application
) : AndroidViewModel(application) {

    fun getAppName(): String {
        val context = getApplication<Application>()
        return context.packageName
    }
}

但实际开发中,更推荐把 Context 相关操作放在 Repository 或其他工具类中。


3. viewModelScope 是什么?

viewModelScope 是绑定到 ViewModel 生命周期的协程作用域。

当 ViewModel 被销毁时,viewModelScope 中的协程会自动取消。

class MainViewModel : ViewModel() {

    fun requestData() {
        viewModelScope.launch {
            // 执行异步任务
        }
    }

    override fun onCleared() {
        super.onCleared()
        // viewModelScope 会在这里自动取消
    }
}

4. LiveData 和 StateFlow 有什么区别?

对比项 LiveData StateFlow
所属 Android Jetpack Kotlin Coroutines
生命周期感知 天然支持 需要 repeatOnLifecycle
是否有初始值 不强制 必须有初始值
是否跨平台
使用场景 传统 Android 项目 Kotlin 协程项目、Compose 项目
数据流能力 较弱 更强

5. LiveData 示例

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class UserLiveDataViewModel : ViewModel() {

    private val repository = UserRepository()

    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user

    private val _loading = MutableLiveData<Boolean>()
    val loading: LiveData<Boolean> = _loading

    fun loadUser() {
        viewModelScope.launch {
            _loading.value = true

            try {
                _user.value = repository.getUser()
            } finally {
                _loading.value = false
            }
        }
    }
}

Activity / Fragment 中观察:

viewModel.user.observe(viewLifecycleOwner) { user ->
    // 更新 UI
}

viewModel.loading.observe(viewLifecycleOwner) { loading ->
    // 显示或隐藏 loading
}

六、ViewModel 的生命周期

1. Activity 中 ViewModel 生命周期

Activity 创建
    ↓
ViewModel 创建
    ↓
Activity 横竖屏切换
    ↓
Activity 销毁并重建
    ↓
ViewModel 仍然存在
    ↓
Activity 真正 finish
    ↓
ViewModel.onCleared()

2. ViewModel 的 onCleared() 什么时候调用?

当 ViewModel 不再被使用时调用。

例如:

  1. Activity finish
  2. Fragment 被真正移除
  3. ViewModelStore 被清空
class MainViewModel : ViewModel() {

    override fun onCleared() {
        super.onCleared()
        // 释放资源
    }
}

七、Activity ViewModel 和 Fragment ViewModel 的区别

1. Fragment 独立 ViewModel

class UserFragment : Fragment() {

    private val viewModel: UserViewModel by viewModels()
}

这个 ViewModel 只属于当前 Fragment。


2. Activity 共享 ViewModel

class UserFragment : Fragment() {

    private val viewModel: SharedViewModel by activityViewModels()
}

多个 Fragment 可以共享同一个 Activity 级别的 ViewModel。


3. 共享 ViewModel 示例

class SharedViewModel : ViewModel() {

    private val _selectedUser = MutableStateFlow<User?>(null)
    val selectedUser: StateFlow<User?> = _selectedUser.asStateFlow()

    fun selectUser(user: User) {
        _selectedUser.value = user
    }
}

Fragment A:

class UserListFragment : Fragment() {

    private val viewModel: SharedViewModel by activityViewModels()

    fun onUserClick(user: User) {
        viewModel.selectUser(user)
    }
}

Fragment B:

class UserDetailFragment : Fragment() {

    private val viewModel: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.selectedUser.collect { user ->
                    // 显示用户详情
                }
            }
        }
    }
}

八、ViewModel 如何传参数?

1. 使用 ViewModelProvider.Factory

class UserViewModel(
    private val userId: Int,
    private val repository: UserRepository
) : ViewModel() {

    fun loadUser() {
        viewModelScope.launch {
            // 根据 userId 加载数据
        }
    }
}

Factory:

class UserViewModelFactory(
    private val userId: Int,
    private val repository: UserRepository
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            return UserViewModel(userId, repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

使用:

class UserDetailFragment : Fragment() {

    private val viewModel: UserViewModel by viewModels {
        UserViewModelFactory(
            userId = 1,
            repository = UserRepository()
        )
    }
}

九、SavedStateHandle 是什么?

SavedStateHandle 用于保存 ViewModel 中需要恢复的小数据。

适合保存:

  1. 页面参数
  2. 搜索关键词
  3. tab 位置
  4. 临时 UI 状态

示例

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.StateFlow

class SearchViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val keyword: StateFlow<String> =
        savedStateHandle.getStateFlow("keyword", "")

    fun updateKeyword(value: String) {
        savedStateHandle["keyword"] = value
    }
}

十、Repository 在 MVVM 中的作用

面试回答

Repository 是数据仓库层,负责统一管理数据来源。

它可以从:

  1. 网络 API 获取数据
  2. 本地数据库获取数据
  3. 缓存获取数据

ViewModel 不直接关心数据来自哪里,只调用 Repository。


示例

class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {

    suspend fun getUser(id: Int): User {
        val localUser = dao.getUser(id)

        if (localUser != null) {
            return localUser
        }

        val remoteUser = api.getUser(id)
        dao.insertUser(remoteUser)

        return remoteUser
    }
}

十一、单次事件怎么处理?

例如:

  1. Toast
  2. 页面跳转
  3. 弹 Dialog

这类事件不适合直接用普通 StateFlow 表示 UI 状态,否则可能出现重复消费。

可以使用 SharedFlow

sealed class UiEvent {
    data class ShowToast(val message: String) : UiEvent()
    data class NavigateToDetail(val userId: Int) : UiEvent()
}

ViewModel:

class MainViewModel : ViewModel() {

    private val _event = MutableSharedFlow<UiEvent>()
    val event = _event.asSharedFlow()

    fun onUserClick(userId: Int) {
        viewModelScope.launch {
            _event.emit(UiEvent.NavigateToDetail(userId))
        }
    }

    fun showError() {
        viewModelScope.launch {
            _event.emit(UiEvent.ShowToast("加载失败"))
        }
    }
}

Fragment:

viewLifecycleOwner.lifecycleScope.launch {
    viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.event.collect { event ->
            when (event) {
                is UiEvent.ShowToast -> {
                    Toast.makeText(requireContext(), event.message, Toast.LENGTH_SHORT).show()
                }

                is UiEvent.NavigateToDetail -> {
                    // findNavController().navigate(...)
                }
            }
        }
    }
}

十二、MVVM 的优点和缺点

优点

  1. UI 和业务逻辑解耦
  2. 代码结构清晰
  3. ViewModel 易于单元测试
  4. 更好地处理生命周期
  5. 适合多人协作开发

缺点

  1. 小项目可能显得复杂
  2. ViewModel 容易变得臃肿
  3. 状态管理不当会导致 UI 混乱
  4. 单次事件处理需要额外设计

十三、ViewModel 面试高频简答

1. ViewModel 是否感知生命周期?

ViewModel 本身不直接感知 Activity / Fragment 的生命周期回调。

但它会在所属的 ViewModelStore 被清除时调用 onCleared()


2. ViewModel 和 onSaveInstanceState 有什么区别?

对比 ViewModel onSaveInstanceState
保存内容 UI 数据、状态 少量可序列化数据
是否能抗配置变化 可以 可以
是否能抗进程被杀 不一定 可以
数据大小 可较大 应该很小
用途 页面状态管理 进程重建恢复

通常两者配合使用,或者使用 SavedStateHandle


3. ViewModel 中能不能做网络请求?

可以。

但 ViewModel 不应该直接调用 Retrofit API,推荐通过 Repository。

class MainViewModel(
    private val repository: UserRepository
) : ViewModel() {

    fun loadData() {
        viewModelScope.launch {
            val result = repository.getUser()
        }
    }
}

4. ViewModel 为什么不能持有 View?

因为 View 的生命周期通常短于 ViewModel。

如果 ViewModel 持有 Activity / Fragment / View 引用,当页面销毁后 ViewModel 还存在,就可能导致内存泄漏。


5. ViewModel 如何避免内存泄漏?

  1. 不持有 Activity、Fragment、View 引用
  2. 不持有短生命周期 Context
  3. 使用 viewModelScope
  4. onCleared() 中释放资源
  5. 长生命周期监听器及时取消注册

十四、完整 MVVM 示例

Api

interface UserApi {
    suspend fun getUser(id: Int): User
}

Dao

interface UserDao {
    suspend fun getUser(id: Int): User?
    suspend fun insertUser(user: User)
}

Repository

class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {

    suspend fun getUser(id: Int): User {
        val cache = dao.getUser(id)

        if (cache != null) {
            return cache
        }

        val remote = api.getUser(id)
        dao.insertUser(remote)

        return remote
    }
}

UiState

sealed interface UserDetailUiState {

    object Loading : UserDetailUiState

    data class Success(
        val user: User
    ) : UserDetailUiState

    data class Error(
        val message: String
    ) : UserDetailUiState
}

ViewModel

class UserDetailViewModel(
    private val repository: UserRepository
) : ViewModel() {

    private val _uiState =
        MutableStateFlow<UserDetailUiState>(UserDetailUiState.Loading)

    val uiState: StateFlow<UserDetailUiState> =
        _uiState.asStateFlow()

    fun loadUser(id: Int) {
        viewModelScope.launch {
            _uiState.value = UserDetailUiState.Loading

            runCatching {
                repository.getUser(id)
            }.onSuccess { user ->
                _uiState.value = UserDetailUiState.Success(user)
            }.onFailure { throwable ->
                _uiState.value = UserDetailUiState.Error(
                    throwable.message ?: "加载失败"
                )
            }
        }
    }
}

Fragment

class UserDetailFragment : Fragment(R.layout.fragment_user_detail) {

    private val viewModel: UserDetailViewModel by viewModels {
        UserDetailViewModelFactory(
            repository = UserRepository(
                api = FakeUserApi(),
                dao = FakeUserDao()
            )
        )
    }

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

        viewModel.loadUser(id = 1)
    }

    private fun observeState() {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is UserDetailUiState.Loading -> {
                            // 显示 Loading
                        }

                        is UserDetailUiState.Success -> {
                            // 显示用户信息
                        }

                        is UserDetailUiState.Error -> {
                            // 显示错误
                        }
                    }
                }
            }
        }
    }
}

Factory

class UserDetailViewModelFactory(
    private val repository: UserRepository
) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserDetailViewModel::class.java)) {
            return UserDetailViewModel(repository) as T
        }

        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

十五、面试中推荐回答模板

如果面试官问:

你怎么理解 MVVM?

可以回答:

MVVM 是一种分层架构,主要分为 Model、View、ViewModel。View 只负责 UI 展示和用户交互,ViewModel 负责处理 UI 状态和业务逻辑,Model 负责数据来源。
在 Android 中,ViewModel 可以配合 LiveData、StateFlow、Repository 使用,帮助我们处理生命周期、配置变化和数据状态管理。这样可以让代码更解耦,也更容易测试和维护。

如果面试官问:

ViewModel 的作用是什么?

可以回答:

ViewModel 主要用于保存和管理 UI 相关数据。它的生命周期独立于 Activity 或 Fragment 的重建,所以横竖屏切换时数据不会丢失。它还可以通过 viewModelScope 执行协程任务,在销毁时自动取消,避免内存泄漏。通常 ViewModel 不直接持有 View 或 Context,而是通过状态流通知 View 更新 UI。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇