ViewModel生命周期管理:构建健壮Android应用的完整指南
引言
在Android应用开发领域,ViewModel作为Android Jetpack架构组件的核心成员,已经成为现代Android应用开发不可或缺的一部分。ViewModel的生命周期管理不仅关系到应用的内存使用效率,更直接影响着用户体验和应用稳定性。本文将深入探讨ViewModel生命周期的各个方面,从基础概念到高级实践,帮助开发者全面掌握这一关键技术。
ViewModel基础概念
什么是ViewModel
ViewModel是一个旨在以生命周期意识的方式存储和管理UI相关数据的类。它允许数据在配置更改(如屏幕旋转)后继续存在,同时帮助将UI控制器(Activity/Fragment)的逻辑与数据操作分离。
ViewModel的设计目标
ViewModel的设计主要解决以下核心问题:
- 数据持久化:在配置更改时保持数据一致性
- 生命周期感知:自动管理资源清理
- UI分离:促进更清晰的架构模式
- 测试友好:便于单元测试和UI测试
ViewModel生命周期详解
生命周期概述
ViewModel的生命周期与其关联的UI控制器(Activity或Fragment)的生命周期紧密相连,但又有所不同。理解这一差异是掌握ViewModel的关键。
生命周期阶段分析
1. 创建阶段
ViewModel的创建通常通过ViewModelProvider实现:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}
}
在这个阶段,ViewModel实例被创建并与特定的Activity或Fragment关联。如果Activity因配置更改而重新创建,同一个ViewModel实例会被重用。
2. 活跃阶段
当关联的UI控制器处于活跃状态(STARTED或RESUMED)时,ViewModel处于活跃阶段。此时,ViewModel可以:
- 观察LiveData变化
- 执行数据加载操作
- 更新UI状态
3. 清理阶段
当关联的UI控制器完全销毁(不再重新创建)时,ViewModel会调用onCleared()方法:
class MainViewModel : ViewModel() {
private val disposable = CompositeDisposable()
override fun onCleared() {
super.onCleared()
disposable.dispose() // 清理资源
}
}
生命周期对比:Activity vs ViewModel
理解Activity和ViewModel生命周期的差异至关重要:
| Activity生命周期 | ViewModel生命周期 | 说明 |
|---|---|---|
| onCreate() | 构造函数 | ViewModel在Activity的onCreate之前或同时创建 |
| onStart() | 活跃状态 | ViewModel随Activity进入活跃状态 |
| onResume() | 活跃状态 | ViewModel保持活跃 |
| onPause() | 活跃状态 | ViewModel仍保持活跃 |
| onStop() | 可能保持活跃 | 如果因配置更改,ViewModel继续存在 |
| onDestroy() | onCleared() | 只有Activity真正结束时才调用onCleared |
ViewModel的创建和管理
ViewModelProvider的工作原理
ViewModelProvider使用Factory模式创建和管理ViewModel实例:
// 自定义ViewModel Factory
class MainViewModelFactory(private val initialValue: Int) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return MainViewModel(initialValue) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
// 使用自定义Factory
val factory = MainViewModelFactory(42)
val viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
ViewModelStore的作用
ViewModelStore负责存储和管理ViewModel实例。每个Activity和Fragment都有各自的ViewModelStore,确保ViewModel实例的正确隔离和生命周期管理。
高级生命周期管理技巧
在Fragment间共享数据
ViewModel可以在Fragment之间共享数据,这在实现复杂UI时特别有用:
class SharedViewModel : ViewModel() {
val selectedItem = MutableLiveData<String>()
fun selectItem(item: String) {
selectedItem.value = item
}
}
// 在多个Fragment中使用同一个ViewModel
class ListFragment : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
}
}
class DetailFragment : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
viewModel.selectedItem.observe(viewLifecycleOwner, { item ->
// 更新UI显示选中的项
})
}
}
处理配置更改
ViewModel天然支持配置更改,但需要正确处理相关逻辑:
class ConfigChangeViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
private var isLoading = false
fun loadData() {
if (isLoading) return
isLoading = true
// 模拟网络请求
viewModelScope.launch {
delay(2000) // 模拟网络延迟
_data.value = "加载完成的数据"
isLoading = false
}
}
}
内存管理和资源清理
避免内存泄漏
虽然ViewModel设计上避免了常见的内存泄漏,但仍需注意:
class SafeViewModel : ViewModel() {
// 正确:使用viewModelScope,自动取消
fun loadData() {
viewModelScope.launch {
// 网络请求或数据库操作
}
}
// 危险:直接使用GlobalScope
fun unsafeLoadData() {
GlobalScope.launch {
// 如果ViewModel销毁,这个协程不会自动取消
}
}
}
资源释放最佳实践
在onCleared()方法中正确释放资源:
class ResourceViewModel : ViewModel() {
private val databaseHelper: DatabaseHelper = // 初始化
private val networkClient: NetworkClient = // 初始化
private val timer: Timer? = null
init {
// 初始化资源
timer = Timer().apply {
scheduleAtFixedRate(object : TimerTask() {
override fun run() {
// 定期任务
}
}, 0, 1000)
}
}
override fun onCleared() {
super.onCleared()
// 释放所有资源
databaseHelper.close()
networkClient.close()
timer?.cancel()
}
}
ViewModel与架构组件集成
结合LiveData使用
ViewModel与LiveData是天作之合:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val _loading = MutableLiveData<Boolean>()
val loading: LiveData<Boolean> = _loading
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
fun loadUsers() {
_loading.value = true
viewModelScope.launch {
try {
val userList = userRepository.getUsers()
_users.value = userList
_error.value = null
} catch (e: Exception) {
_error.value = "加载失败: ${e.message}"
} finally {
_loading.value = false
}
}
}
}
与Room数据库配合
ViewModel可以很好地与Room持久化库集成:
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getUsers(): LiveData<List<User>>
@Insert
suspend fun insertUser(user: User)
}
class UserViewModel(application: Application) : AndroidViewModel(application) {
private val userRepository: UserRepository
init {
val userDao = UserDatabase.getDatabase(application).userDao()
userRepository = UserRepository(userDao)
}
val users: LiveData<List<User>> = userRepository.users
fun insertUser(user: User) {
viewModelScope.launch {
userRepository.insert(user)
}
}
}
测试ViewModel
单元测试
ViewModel的测试相对简单,因为不依赖Android组件:
@RunWith(JUnit4::class)
class UserViewModelTest {
private lateinit var viewModel: UserViewModel
private val userRepository = mockk<UserRepository>()
@Before
fun setup() {
viewModel = UserViewModel(userRepository)
}
@Test
fun `loadUsers should update live data`() = runTest {
// Given
val expectedUsers = listOf(User("1", "John"))
coEvery { userRepository.getUsers() } returns expectedUsers
// When
viewModel.loadUsers()
// Then
assertThat(viewModel.users.value).isEqualTo(expectedUsers)
assertThat(viewModel.loading.value).isFalse()
assertThat(viewModel.error.value).isNull()
}
}

评论框