一、ViewModel 概述
1.1 什么是 ViewModel
ViewModel 是 Android Jetpack 架构组件之一,用于以生命周期感知的方式存储和管理 UI 相关数据。
1.2 主要特性
- 生命周期感知:自动管理数据生命周期
- 配置变更存活:屏幕旋转等配置变更时数据不会丢失
- UI 数据存储:专门为 UI 准备和管理数据
- 分离关注点:帮助实现 MVVM 架构模式
二、ViewModel 的核心作用
2.1 解决的主要问题
// 传统方式的问题:数据在配置变更时丢失
class MainActivity : AppCompatActivity() {
private var counter = 0 // 旋转屏幕时会重置!
override fun onCreate(savedInstanceState: Bundle?) {
// ...
}
}
2.2 ViewModel 的优势
| 特性 | 传统方式 | ViewModel |
|---|---|---|
| 配置变更 | 数据丢失 | 数据保留 |
| 生命周期 | 手动管理 | 自动管理 |
| 内存泄漏 | 易发生 | 不易发生 |
| 测试难度 | 困难 | 容易测试 |
三、基本用法
3.1 添加依赖
// build.gradle (app)
dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0"
// 或使用最新版本
}
3.2 创建 ViewModel
// 1. 简单 ViewModel
class MyViewModel : ViewModel() {
private val _counter = MutableLiveData(0)
val counter: LiveData<Int> = _counter
fun increment() {
_counter.value = (_counter.value ?: 0) + 1
}
// ViewModel 销毁时的清理工作
override fun onCleared() {
super.onCleared()
// 释放资源
}
}
// 2. 带参数的 ViewModel(需要使用 Factory)
class UserViewModel(private val userId: String) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
fun loadUser() {
// 加载用户数据
}
}
3.3 在 Activity/Fragment 中使用
// Activity 中使用
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 获取 ViewModel 实例
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// 观察 LiveData
viewModel.counter.observe(this) { count ->
updateCounterUI(count)
}
// 触发数据变化
button.setOnClickListener {
viewModel.increment()
}
}
}
// Fragment 中使用
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels() // Kotlin 扩展方式
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.counter.observe(viewLifecycleOwner) { count ->
// 更新 UI
}
}
}
四、ViewModel 生命周期
4.1 生命周期示意图
Activity/Fragment Created
↓
ViewModel Created (首次创建)
↓
Activity/Fragment Started
↓
Activity/Fragment Resumed
↓
← 屏幕旋转等配置变更 →
(Activity/Fragment 销毁重建,但 ViewModel 保留)
↓
Activity/Fragment Destroyed(非配置变更)
↓
ViewModel onCleared() 调用
4.2 生命周期感知示例
class MyViewModel : ViewModel() {
private val _state = MutableLiveData(ViewModelState.IDLE)
init {
// ViewModel 创建时调用
loadInitialData()
}
override fun onCleared() {
// ViewModel 销毁时调用(当 Activity/Fragment 永久销毁时)
cleanupResources()
super.onCleared()
}
}
五、ViewModelFactory
5.1 为什么需要 Factory
当 ViewModel 需要参数时,必须使用 ViewModelProvider.Factory
5.2 自定义 Factory
// 1. 创建 Factory
class UserViewModelFactory(
private val userId: String,
private val repository: UserRepository
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
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")
}
}
// 2. 使用 Factory
class UserActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userId = intent.getStringExtra("USER_ID") ?: ""
val repository = UserRepository()
val factory = UserViewModelFactory(userId, repository)
viewModel = ViewModelProvider(this, factory).get(UserViewModel::class.java)
}
}
六、ViewModel 的作用域
6.1 不同的作用域
// 1. Activity 作用域 - 同一个 Activity 的多个 Fragment 共享
class SharedActivity : AppCompatActivity() {
val sharedViewModel: SharedViewModel by viewModels()
}
class FragmentA : Fragment() {
// 获取 Activity 级别的 ViewModel
private val activityViewModel: SharedViewModel by activityViewModels()
}
// 2. Fragment 作用域 - 仅限于单个 Fragment
class MyFragment : Fragment() {
private val fragmentViewModel: MyViewModel by viewModels()
}
// 3. Navigation Graph 作用域
class NavFragment : Fragment() {
// 需要在 nav_graph.xml 中声明
private val navViewModel: NavViewModel by navGraphViewModels(R.id.nav_graph)
}
6.2 Navigation 组件中的 ViewModel
<!-- navigation/nav_graph.xml -->
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:viewModelScope="true">
<!-- ... -->
</navigation>
七、ViewModel 与 LiveData/StateFlow 结合
7.1 配合 LiveData
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val _loading = MutableLiveData(false)
val loading: LiveData<Boolean> = _loading
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
fun loadUsers() {
_loading.value = true
viewModelScope.launch {
try {
val result = repository.getUsers()
_users.value = result
_error.value = null
} catch (e: Exception) {
_error.value = e.message
} finally {
_loading.value = false
}
}
}
}
7.2 配合 StateFlow(推荐)
class UserViewModel(private val repository: UserRepository) : ViewModel() {
// 私有可变的 StateFlow
private val _uiState = MutableStateFlow(UserUiState())
// 公开只读的 StateFlow
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
// 密封类定义 UI 状态
data class UserUiState(
val users: List<User> = emptyList(),
val loading: Boolean = false,
val error: String? = null
)
fun loadUsers() {
_uiState.update { it.copy(loading = true, error = null) }
viewModelScope.launch {
repository.getUsers()
.onSuccess { users ->
_uiState.update { it.copy(users = users, loading = false) }
}
.onFailure { error ->
_uiState.update {
it.copy(error = error.message, loading = false)
}
}
}
}
}
八、最佳实践
8.1 推荐的架构模式
// 1. ViewModel 负责业务逻辑
class ProductViewModel(
private val getProductsUseCase: GetProductsUseCase,
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 2. 使用 StateFlow 管理状态
private val _state = MutableStateFlow(ProductState())
val state = _state.asStateFlow()
// 3. 使用 SavedStateHandle 保存临时状态
var searchQuery: String
get() = savedStateHandle["search_query"] ?: ""
set(value) { savedStateHandle["search_query"] = value }
// 4. 使用协程处理异步操作
fun loadProducts() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
val result = getProductsUseCase(searchQuery)
_state.update { it.copy(
products = result,
isLoading = false
) }
}
}
}
// 5. 数据类定义状态
data class ProductState(
val products: List<Product> = emptyList(),
val isLoading: Boolean = false,
val error: String? = null
)
8.2 注意事项
- 不要持有 Context 引用(如果需要,使用 AndroidViewModel)
- 避免直接暴露 MutableLiveData/MutableStateFlow
- 使用单向数据流(UI → ViewModel → Repository)
- 正确处理配置变更(使用 SavedStateHandle)
- 及时清理资源(重写 onCleared 方法)
8.3 使用 AndroidViewModel(需要 Context 时)
class MyAndroidViewModel(
application: Application,
private val repository: MyRepository
) : AndroidViewModel(application) {
// 可以安全地使用 application context
private val context = getApplication<Application>().applicationContext
fun doSomething() {
// 使用 context
val packageName = context.packageName
}
}
九、测试 ViewModel
单元测试示例:
@RunWith(JUnit4::class)
class UserViewModelTest {
private lateinit var viewModel: UserViewModel
private val mockRepository = mockk<UserRepository>()
@Before
fun setup() {
viewModel = UserViewModel(mockRepository)
}
@Test
fun myTest = runTest {
// Given
val mockUsers = listOf(User("1", "John"))
coEvery { mockRepository.getUsers() } returns Result.success(mockUsers)
// When
viewModel.loadUsers()
// Then
viewModel.uiState.test {
val initialState = awaitItem()
assertTrue(initialState.loading)
val finalState = awaitItem()
assertFalse(finalState.loading)
assertEquals(mockUsers, finalState.users)
}
}
}
十、总结
ViewModel 是 Android 架构的核心组件:
- 管理 UI 数据的生命周期
- 在配置变更时保留数据
- 促进关注点分离(UI 逻辑 vs 业务逻辑)
- 便于测试(不依赖 Android 组件)
- 支持数据共享(在 Fragment 之间)
正确使用 ViewModel 可以大大提高应用的可维护性、可测试性和稳定性。