1. ViewModel 概述
ViewModel 是 Android Jetpack 组件库中的一个核心组件,它的主要设计目标是:
- 分离关注点:将 UI 相关的数据逻辑从 UI 控制器(Activity/Fragment)中分离出来
- 生命周期感知:在配置更改(如屏幕旋转)期间保留数据
- UI 状态管理:提供一个存储和管理 UI 状态的中心点
- 组件间通信:简化 Fragment 之间的通信
2. ViewModel 实例的构建
2.1 基本实现
创建一个 ViewModel 类非常简单,只需继承 ViewModel 基类:
class MyViewModel : ViewModel() {
// 数据存储和业务逻辑
private val _counter = MutableLiveData(0)
val counter: LiveData<Int> get() = _counter
fun incrementCounter() {
_counter.value = (_counter.value ?: 0) + 1
}
}
2.2 使用 ViewModelProvider
在 Activity 或 Fragment 中获取 ViewModel 实例:
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)
// 方法 1:使用 ViewModelProvider
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
// 观察数据变化
viewModel.counter.observe(this) { count ->
textView.text = count.toString()
}
}
}
2.3 使用 ViewModelFactory
当 ViewModel 需要构造函数参数时,需要使用 ViewModelProvider.Factory:
class MyViewModel(private val repository: UserRepository) : ViewModel() {
// ViewModel 逻辑
}
class MyViewModelFactory(private val repository: UserRepository) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
return MyViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
// 在 Activity/Fragment 中使用
val viewModel = ViewModelProvider(this, MyViewModelFactory(repository))[MyViewModel::class.java]
2.4 使用 KTX 扩展函数
通过 Android KTX,可以使用更简洁的语法:
// 在 Activity 中
val viewModel by viewModels<MyViewModel>()
// 带 Factory 的版本
val viewModel by viewModels<MyViewModel> {
MyViewModelFactory(repository)
}
// 在 Fragment 中
val viewModel by activityViewModels<MyViewModel>() // 共享 Activity 的 ViewModel
val viewModel by viewModels<MyViewModel>() // Fragment 自己的 ViewModel
3. ViewModel 的使用场景
3.1 数据保存与配置更改
ViewModel 最基本的用途是在配置更改期间保留数据:
class ProductListViewModel : ViewModel() {
private val _products = MutableLiveData<List<Product>>()
val products: LiveData<List<Product>> get() = _products
fun loadProducts() {
// 模拟网络请求
viewModelScope.launch {
val result = repository.getProducts()
_products.value = result
}
}
}
// 即使屏幕旋转,也不会重新加载数据
class ProductListActivity : AppCompatActivity() {
private val viewModel by viewModels<ProductListViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 只在首次创建时加载数据
if (savedInstanceState == null) {
viewModel.loadProducts()
}
viewModel.products.observe(this) { products ->
// 更新 UI
}
}
}
3.2 Fragment 间通信
ViewModel 是在 Fragment 之间共享数据的理想方式:
// 共享的 ViewModel
class SharedViewModel : ViewModel() {
private val _selectedItemId = MutableLiveData<Long>()
val selectedItemId: LiveData<Long> get() = _selectedItemId
fun selectItem(itemId: Long) {
_selectedItemId.value = itemId
}
}
// 第一个 Fragment
class ListFragment : Fragment() {
private val sharedViewModel by activityViewModels<SharedViewModel>()
fun onItemClick(itemId: Long) {
sharedViewModel.selectItem(itemId)
// 导航到详情 Fragment
}
}
// 第二个 Fragment
class DetailFragment : Fragment() {
private val sharedViewModel by activityViewModels<SharedViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel.selectedItemId.observe(viewLifecycleOwner) { itemId ->
loadItemDetails(itemId)
}
}
}
3.3 长时间运行的操作
ViewModel 可以安全地处理长时间运行的操作,如网络请求或数据库查询:
class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
private val _taskResult = MutableLiveData<Result<Task>>()
val taskResult: LiveData<Result<Task>> get() = _taskResult
fun executeLongRunningTask(taskId: String) {
viewModelScope.launch {
_taskResult.value = Result.Loading
try {
val result = repository.executeTask(taskId)
_taskResult.value = Result.Success(result)
} catch (e: Exception) {
_taskResult.value = Result.Error(e)
}
}
}
}
4. ViewModel 的生命周期控制
4.1 ViewModel 的生命周期图解
ViewModel 的生命周期比创建它的 Activity/Fragment 更长。具体来说:
-
Activity 场景:从首次调用
ViewModelProvider开始,直到 Activity 被销毁(非配置更改导致的销毁) -
Fragment 场景:从首次调用
ViewModelProvider开始,直到 Fragment 被移除 - 配置更改:ViewModel 实例在屏幕旋转、语言切换等配置更改期间保持不变
4.2 onCleared() 方法
当 ViewModel 即将被销毁时,会调用 onCleared() 方法,可以在这里执行清理操作:
class MyViewModel : ViewModel() {
private val disposable = CompositeDisposable()
fun startObserving() {
// 添加订阅
disposable.add(/* 某些 RxJava 订阅 */)
}
override fun onCleared() {
super.onCleared()
// 清理资源
disposable.clear()
}
}
4.3 使用 viewModelScope
ViewModel 提供了内置的协程作用域 viewModelScope,它会在 ViewModel 被销毁时自动取消所有协程:
class MyViewModel : ViewModel() {
fun fetchData() {
viewModelScope.launch {
// 执行协程操作
// 当 ViewModel 销毁时,此协程会自动取消
}
}
}
5. 在 Compose 中使用 ViewModel
5.1 基本用法
在 Jetpack Compose 中使用 ViewModel 非常简单:
@Composable
fun MyComposableScreen() {
// 使用 viewModel() 函数获取 ViewModel
val viewModel: MyViewModel = viewModel()
// 观察状态
val uiState by viewModel.uiState.collectAsState()
// UI 渲染
when (uiState) {
is UiState.Loading -> LoadingScreen()
is UiState.Success -> SuccessScreen(uiState.data)
is UiState.Error -> ErrorScreen(uiState.exception)
}
}
// ViewModel 定义
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
init {
loadData()
}
private fun loadData() {
viewModelScope.launch {
try {
val data = repository.getData()
_uiState.update { UiState.Success(data) }
} catch (e: Exception) {
_uiState.update { UiState.Error(e) }
}
}
}
}
5.2 在 Compose 中共享 ViewModel
在多个 Composable 之间共享同一个 ViewModel 实例:
// 父级 Composable
@Composable
fun ParentScreen() {
// 在这里获取 ViewModel
val sharedViewModel: SharedViewModel = viewModel()
Column {
// 子 Composable 可以接收同一个 ViewModel 实例
ChildOneScreen(viewModel = sharedViewModel)
ChildTwoScreen(viewModel = sharedViewModel)
}
}
@Composable
fun ChildOneScreen(viewModel: SharedViewModel) {
// 使用传入的 ViewModel
}
@Composable
fun ChildTwoScreen(viewModel: SharedViewModel) {
// 使用传入的 ViewModel
}
5.3 Compose 中的状态管理
推荐在 ViewModel 中使用 StateFlow 或 Flow,并在 Composable 中使用 collectAsState() 进行观察:
class ComposeViewModel : ViewModel() {
// 使用 StateFlow 而不是 LiveData
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> = _counter
fun increment() {
_counter.update { it + 1 }
}
}
@Composable
fun CounterScreen() {
val viewModel: ComposeViewModel = viewModel()
// 使用 collectAsState() 观察 StateFlow
val count by viewModel.counter.collectAsState()
Button(onClick = { viewModel.increment() }) {
Text(text = "Count: $count")
}
}
6. 在单 Activity 应用中使用 ViewModel
6.1 多 Fragment 共享 ViewModel
在单 Activity 多 Fragment 架构中,ViewModel 是实现组件间通信的理想选择:
// MainActivity 作为容器
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 初始化导航组件
val navController = findNavController(R.id.nav_host_fragment)
setupActionBarWithNavController(navController)
}
}
// 共享的应用级 ViewModel
class AppViewModel : ViewModel() {
private val _user = MutableLiveData<User?>(null)
val user: LiveData<User?> = _user
fun login(username: String, password: String) {
viewModelScope.launch {
try {
val userData = repository.login(username, password)
_user.value = userData
} catch (e: Exception) {
// 处理错误
}
}
}
}
// 各 Fragment 可以访问共享的 ViewModel
class ProfileFragment : Fragment() {
private val appViewModel by activityViewModels<AppViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
appViewModel.user.observe(viewLifecycleOwner) { user ->
user?.let { updateUI(it) }
}
}
}
6.2 使用 Navigation 组件集成
结合 Navigation 组件,ViewModel 可以在导航图中更有效地共享:
// 在 build.gradle 中添加依赖
implementation "androidx.navigation:navigation-fragment-ktx:2.5.3"
implementation "androidx.navigation:navigation-ui-ktx:2.5.3"
// 在 Fragment 中使用
class DetailFragment : Fragment() {
// 方式1:获取自己的 ViewModel
private val detailViewModel by viewModels<DetailViewModel>()
// 方式2:获取共享的 Activity ViewModel
private val sharedViewModel by activityViewModels<SharedViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 从导航参数获取数据
val args: DetailFragmentArgs by navArgs()
val itemId = args.itemId
detailViewModel.loadItemDetails(itemId)
}
}
6.3 分层 ViewModel 架构
在复杂应用中,可以采用分层 ViewModel 架构:
// 应用级 ViewModel(保存用户会话等全局状态)
class AppViewModel(application: Application) : AndroidViewModel(application) {
// 全局状态管理
}
// 功能模块级 ViewModel
class UserModuleViewModel : ViewModel() {
// 用户相关功能的状态管理
}
// 页面级 ViewModel
class ProfileViewModel(private val userRepository: UserRepository) : ViewModel() {
// 个人资料页面的状态管理
}
7. ViewModel 与其他组件的集成
7.1 与 LiveData 集成
ViewModel 通常与 LiveData 结合使用:
class UserViewModel(private val repository: UserRepository) : ViewModel() {
private val _user = MutableLiveData<User?>(null)
val user: LiveData<User?> get() = _user
private val _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean> get() = _isLoading
fun fetchUserData(userId: String) {
_isLoading.value = true
viewModelScope.launch {
try {
val data = repository.getUserById(userId)
_user.value = data
} finally {
_isLoading.value = false
}
}
}
}
7.2 与 Room 数据库集成
ViewModel 与 Room 数据库结合使用:
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): LiveData<List<User>>
}
class UserRepository(private val userDao: UserDao) {
fun getAllUsers() = userDao.getAllUsers()
}
class UserListViewModel(private val repository: UserRepository) : ViewModel() {
// Room 返回的 LiveData 会在后台线程执行查询
val users = repository.getAllUsers()
}
7.3 与 DataBinding 集成
ViewModel 可以与 DataBinding 无缝集成:
// 在 ViewModel 中提供命令
class LoginViewModel : ViewModel() {
val username = MutableLiveData("")
val password = MutableLiveData("")
val loginCommand = object : ViewModelCommand {
override fun execute() {
// 执行登录逻辑
}
}
}
// 在布局文件中绑定
<layout>
<data>
<variable
name="viewModel"
type="com.example.LoginViewModel" />
</data>
<EditText
android:text="@{viewModel.username}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:onClick="@{() -> viewModel.loginCommand.execute()}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</layout>
// 在 Activity 中设置绑定
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityLoginBinding = DataBindingUtil.setContentView(this, R.layout.activity_login)
val viewModel = ViewModelProvider(this)[LoginViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
}
}
8. ViewModel 最佳实践
8.1 关注点分离
保持 ViewModel 专注于 UI 状态管理:
// 好的做法:ViewModel 只负责 UI 状态管理
class ProductViewModel(private val productUseCase: GetProductsUseCase) : ViewModel() {
private val _state = MutableStateFlow<ProductState>(ProductState.Loading)
val state: StateFlow<ProductState> = _state
fun loadProducts() {
viewModelScope.launch {
try {
val products = productUseCase.execute()
_state.update { ProductState.Success(products) }
} catch (e: Exception) {
_state.update { ProductState.Error(e.message) }
}
}
}
}
// 业务逻辑被封装在 UseCase 中
class GetProductsUseCase(private val repository: ProductRepository) {
suspend fun execute(): List<Product> {
// 业务逻辑
return repository.getProducts()
}
}
8.2 使用 UiState 模式
定义清晰的 UI 状态类:
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
data class Success<out T>(val data: T) : UiState<T>()
data class Error(val message: String?) : UiState<Nothing>()
}
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState<MyData>>(UiState.Loading)
val uiState: StateFlow<UiState<MyData>> = _uiState
}
8.3 避免内存泄漏
- 不要在 ViewModel 中持有 Activity/Fragment 的引用
- 不使用 Context(除非使用 AndroidViewModel)
- 使用 viewModelScope 管理协程
- 在 onCleared() 中释放资源
// 错误示例
class BadViewModel(private val context: Context) : ViewModel() {
// 持有 Context 引用可能导致内存泄漏
}
// 正确示例
class GoodViewModel(application: Application) : AndroidViewModel(application) {
// 可以安全地使用 application Context
private val appContext: Context = application
}
8.4 测试 ViewModel
ViewModel 设计为易于测试:
@RunWith(JUnit4::class)
class MyViewModelTest {
private lateinit var viewModel: MyViewModel
private lateinit var repository: MockRepository
@Before
fun setup() {
repository = MockRepository()
viewModel = MyViewModel(repository)
}
@Test
fun `loading products updates state correctly`() = runBlockingTest {
// 准备数据
val testProducts = listOf(Product(1, "Test"))
repository.setProducts(testProducts)
// 执行操作
viewModel.loadProducts()
// 验证结果
assertThat(viewModel.uiState.value).isInstanceOf(UiState.Success::class.java)
val successState = viewModel.uiState.value as UiState.Success
assertThat(successState.data).isEqualTo(testProducts)
}
}
9. 总结
ViewModel 是 Android Jetpack 中一个强大的组件,它通过以下方式简化了 Android 应用开发:
- 配置更改生存:在屏幕旋转等配置更改期间保存数据
- 清晰的架构:促进关注点分离,提高代码可维护性
- 生命周期感知:自动处理组件生命周期,避免内存泄漏
- 组件通信:简化 Fragment 之间以及不同 UI 组件之间的通信
通过遵循本文介绍的最佳实践和模式,您可以充分利用 ViewModel 构建稳健、可维护的 Android 应用。