一、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 提供的组件,主要作用:
- 保存和管理 UI 相关数据
- 屏蔽配置变化,例如横竖屏切换
- 分离 UI 逻辑和 Activity / Fragment
- 配合 LiveData、StateFlow 暴露状态
- 在
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 不再被使用时调用。
例如:
- Activity finish
- Fragment 被真正移除
- 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 中需要恢复的小数据。
适合保存:
- 页面参数
- 搜索关键词
- tab 位置
- 临时 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 是数据仓库层,负责统一管理数据来源。
它可以从:
- 网络 API 获取数据
- 本地数据库获取数据
- 缓存获取数据
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
}
}
十一、单次事件怎么处理?
例如:
- Toast
- 页面跳转
- 弹 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 的优点和缺点
优点
- UI 和业务逻辑解耦
- 代码结构清晰
- ViewModel 易于单元测试
- 更好地处理生命周期
- 适合多人协作开发
缺点
- 小项目可能显得复杂
- ViewModel 容易变得臃肿
- 状态管理不当会导致 UI 混乱
- 单次事件处理需要额外设计
十三、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 如何避免内存泄漏?
- 不持有 Activity、Fragment、View 引用
- 不持有短生命周期 Context
- 使用
viewModelScope - 在
onCleared()中释放资源 - 长生命周期监听器及时取消注册
十四、完整 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。