Android ViewModel 的简单用法

61 阅读4分钟

一、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 注意事项

  1. 不要持有 Context 引用(如果需要,使用 AndroidViewModel)
  2. 避免直接暴露 MutableLiveData/MutableStateFlow
  3. 使用单向数据流(UI → ViewModel → Repository)
  4. 正确处理配置变更(使用 SavedStateHandle)
  5. 及时清理资源(重写 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 架构的核心组件:

  1. 管理 UI 数据的生命周期
  2. 在配置变更时保留数据
  3. 促进关注点分离(UI 逻辑 vs 业务逻辑)
  4. 便于测试(不依赖 Android 组件)
  5. 支持数据共享(在 Fragment 之间)

正确使用 ViewModel 可以大大提高应用的可维护性、可测试性和稳定性。