扩展 ViewModel 知识体系:进阶架构组件深度解析
除了标准 ViewModel 和 SharedViewModel,Android Jetpack 提供了一系列强大的架构组件。以下是完整解析:
1. AndroidViewModel:访问应用上下文的特殊 ViewModel
核心特性和使用场景
kotlin
class SystemInfoViewModel(application: Application) : AndroidViewModel(application) {
// 安全访问 Application Context
private val appContext = getApplication<Application>().applicationContext
// 示例:获取设备信息
fun getDeviceInfo(): String {
val pm = appContext.packageManager
return "Model: ${Build.MODEL}\nOS: ${Build.VERSION.RELEASE}"
}
// 示例:访问资源
fun getAppName(): String {
return appContext.resources.getString(R.string.app_name)
}
}
使用规范
操作 | 安全做法 | 危险做法 |
---|---|---|
获取上下文 | getApplication<Application>() |
持有 Activity/Fragment 引用 |
资源访问 | 仅访问应用级资源 | 尝试访问视图相关资源 |
系统服务 | getSystemService(Class<T>) |
持有服务长引用 |
2. SavedStateViewModel:配置更改时保留状态
架构原理
graph LR
A[Activity销毁] --> B[保存状态到Bundle]
C[重建Activity] --> D[恢复ViewModel状态]
B -->|SavedStateHandle| E[ViewModel]
D --> E
实现示例
kotlin
class UserFormViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 使用状态键值定义
companion object {
private const val USERNAME_KEY = "username"
private const val EMAIL_KEY = "email"
}
var username: String
get() = savedStateHandle.get(USERNAME_KEY) ?: ""
set(value) = savedStateHandle.set(USERNAME_KEY, value)
var email: String
get() = savedStateHandle.get(EMAIL_KEY) ?: ""
set(value) = savedStateHandle.set(EMAIL_KEY, value)
// 重置表单状态
fun clearForm() {
savedStateHandle.remove<Any>(USERNAME_KEY)
savedStateHandle.remove<Any>(EMAIL_KEY)
}
}
3. HiltViewModel:依赖注入的最佳实践
依赖注入架构
graph TD
A[Database] --> B[Repository]
C[Network API] --> B
B --> D[ViewModel]
D --> E[Activity/Fragment]
F[Hilt 模块] -->|提供依赖| D
实现示例
kotlin
// 定义依赖项
@Module
@InstallIn(ViewModelComponent::class)
object UserModule {
@Provides
fun provideUserRepo(db: AppDatabase): UserRepository {
return UserRepository(db.userDao())
}
}
// 使用 HiltViewModel
@HiltViewModel
class UserProfileViewModel @Inject constructor(
private val userRepo: UserRepository
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
fun loadUser(userId: String) {
viewModelScope.launch {
_user.value = userRepo.getUser(userId)
}
}
}
4. Navigation 图作用域 ViewModel
多模块架构实现
kotlin
// 注册导航图作用域的 ViewModel
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_nav"
app:viewModelScope="graph_view_model">
<fragment android:id="@+id/userList" ... />
<fragment android:id="@+id/userDetail" ... />
</navigation>
// 在片段中获取
class UserListFragment : Fragment() {
private val navViewModel: SharedUserViewModel by navGraphViewModels(R.id.main_nav)
fun onUserSelected(user: User) {
navViewModel.setSelectedUser(user)
findNavController().navigate(R.id.action_to_detail)
}
}
class UserDetailFragment : Fragment() {
private val navViewModel: SharedUserViewModel by navGraphViewModels(R.id.main_nav)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
navViewModel.selectedUser.observe(viewLifecycleOwner) { user ->
// 显示用户详情
}
}
}
5. Compose 专属 ViewModel:现代 UI 架构
Composable 中的状态管理
kotlin
@Composable
fun UserProfileScreen(
userId: String,
viewModel: UserProfileViewModel = viewModel(
factory = UserProfileViewModel.provideFactory(userId)
)
) {
val userState by viewModel.userState.collectAsState()
when(val state = userState) {
is UserState.Loading -> LoadingSpinner()
is UserState.Success -> UserProfile(state.user)
is UserState.Error -> ErrorView(state.message)
}
}
// ViewModel 工厂
class UserProfileViewModel @AssistedInject constructor(
@Assisted private val userId: String,
private val repo: UserRepository
) : ViewModel() {
val userState = MutableStateFlow<UserState>(UserState.Loading)
init {
loadUser()
}
private fun loadUser() {
viewModelScope.launch {
userState.value = try {
UserState.Success(repo.getUser(userId))
} catch (e: Exception) {
UserState.Error(e.message ?: "Unknown error")
}
}
}
@AssistedFactory
interface Factory {
fun create(userId: String): UserProfileViewModel
}
companion object {
fun provideFactory(userId: String): Factory {
return object : Factory {
override fun create(userId: String): UserProfileViewModel {
val app = LocalContext.current.applicationContext as Application
val repo = (app as AppDependencies).provideUserRepo()
return UserProfileViewModel(userId, repo)
}
}
}
}
}
6. ViewModel 领域扩展:解决复杂场景
模式 1:自动加载状态管理
kotlin
abstract class LoadableViewModel<T> : ViewModel() {
private val _loading = MutableStateFlow(false)
val loading: StateFlow<Boolean> = _loading.asStateFlow()
private val _data = MutableStateFlow<T?>(null)
val data: StateFlow<T?> = _data.asStateFlow()
private val _error = MutableStateFlow<Throwable?>(null)
val error: StateFlow<Throwable?> = _error.asStateFlow()
protected suspend fun executeLoad(block: suspend () -> T) {
_loading.value = true
_error.value = null
try {
_data.value = block()
} catch (e: Exception) {
_error.value = e
} finally {
_loading.value = false
}
}
}
class ProductViewModel : LoadableViewModel<List<Product>>() {
fun loadProducts() {
viewModelScope.launch {
executeLoad {
productRepo.getFeaturedProducts()
}
}
}
}
模式 2:时间旅行调试(测试专用)
kotlin
class DebuggableViewModel : ViewModel() {
private val stateHistory = mutableListOf<AppState>()
private val _currentState = MutableLiveData<AppState>()
val currentState: LiveData<AppState> = _currentState
fun updateState(newState: AppState) {
stateHistory.add(newState)
_currentState.value = newState
}
// 测试环境专用方法
@VisibleForTesting
fun rewindState(step: Int) {
if (step < stateHistory.size) {
_currentState.value = stateHistory[stateHistory.size - step - 1]
}
}
// 重置时间线
@VisibleForTesting
fun resetHistory() {
stateHistory.clear()
}
}
7. 性能优化策略:高级技巧
内存优化表
模式 | 内存占用 | 响应速度 | 适用场景 |
---|---|---|---|
StateFlow | 低 | 高 | 频繁更新状态 |
SharedFlow(replay=1) | 中 | 高 | 单事件系统 |
LiveData | 最低 | 中 | 简单 UI 更新 |
ConflatedBroadcastChannel | 低 | 最高 | 高频事件 |
ObservableField | 最低 | 高 | 数据绑定简单属性 |
惰性初始化技术
kotlin
class OptimizedViewModel : ViewModel() {
private val _heavyData by lazy {
val data = HeavyDataSet()
initializeHeavyData(data)
MutableStateFlow(data)
}
val heavyData: StateFlow<HeavyDataSet> = _heavyData
private val _resourceCache = mutableMapOf<String, Resource>()
fun getResource(resourceId: String): Resource {
return _resourceCache.getOrPut(resourceId) {
// 模拟耗时加载
Thread.sleep(50)
Resource(resourceId)
}
}
override fun onCleared() {
// 清理缓存资源
_resourceCache.clear()
super.onCleared()
}
}
8. 测试策略:JUnit5 + Turbine 高级测试
ViewModel 测试套件
kotlin
@ExperimentalCoroutinesApi
@ExtendWith(MockKExtension::class)
class ProductViewModelTest {
@MockK
lateinit var mockRepo: ProductRepository
private lateinit var viewModel: ProductViewModel
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
Dispatchers.setMain(StandardTestDispatcher())
}
@AfterEach
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `当加载产品时 应显示加载状态`() = runTest {
// 模拟延时
coEvery { mockRepo.getFeaturedProducts() } coAnswers {
delay(1000)
listOf(Product("1", "Sample"))
}
viewModel = ProductViewModel(mockRepo)
viewModel.loadProducts()
// 使用Turbine测试流
viewModel.loading.test {
// 验证初始状态
assertEquals(false, awaitItem())
// 验证加载状态
assertEquals(true, awaitItem())
// 验证完成状态
assertEquals(false, awaitItem())
cancelAndConsumeRemainingEvents()
}
viewModel.data.test {
// 验证初始状态
assertEquals(null, awaitItem())
// 验证产品数据
assertEquals(1, awaitItem()?.size)
cancelAndConsumeRemainingEvents()
}
}
@Test
fun `加载失败时应正确处理错误`() = runTest {
val testException = RuntimeException("Network error")
coEvery { mockRepo.getFeaturedProducts() } throws testException
viewModel = ProductViewModel(mockRepo)
viewModel.loadProducts()
viewModel.error.test {
// 验证初始状态
assertEquals(null, awaitItem())
// 验证错误状态
assertSame(testException, awaitItem())
cancelAndConsumeRemainingEvents()
}
}
}
9. 跨平台方案:KMM 中的 ViewModel
共享业务逻辑实现
kotlin
// 通用模块 - commonMain
expect abstract class KMMViewModel() {
val viewModelScope: CoroutineScope
protected open fun onCleared()
}
// Android 实现 - androidMain
actual abstract class KMMViewModel actual constructor() : ViewModel() {
actual override val viewModelScope: CoroutineScope = this.viewModelScope
actual override fun onCleared() {
super.onCleared()
}
}
// iOS 实现 - iosMain
actual abstract class KMMViewModel actual constructor() {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
actual val viewModelScope: CoroutineScope = coroutineScope
actual open fun onCleared() {
coroutineScope.cancel()
}
}
// 使用示例
class SharedProductViewModel : KMMViewModel() {
private val _products = MutableStateFlow<List<Product>>(emptyList())
val products: StateFlow<List<Product>> = _products.asStateFlow()
fun loadProducts() {
viewModelScope.launch {
val result = repository.getProducts()
_products.value = result
}
}
override fun onCleared() {
repository.cancelRequests()
super.onCleared()
}
}
架构选择决策树
graph TD
A[需求场景] --> B{需要跨组件共享?}
B -->|是| C[使用SharedViewModel]
B -->|否| D{需要访问应用上下文?}
D -->|是| E[使用AndroidViewModel]
D -->|否| F{需要保存状态?}
F -->|是| G[使用SavedStateViewModel]
F -->|否| H{需要依赖注入?}
H -->|是| I[使用HiltViewModel]
H -->|否| J{Jetpack Compose项目?}
J -->|是| K[使用Compose viewModel]
J -->|否| L[使用普通ViewModel]
C --> M{在导航图中共享?}
M -->|是| N[使用navGraphViewModels]
M -->|否| O[使用activityViewModels]
H --> P{需要多模块依赖?}
P -->|是| Q[使用Hilt自定义组件]
通过掌握这些进阶 ViewModel 模式,可以构建:
- 复杂状态管理:处理多步骤流程和事务性操作
- 跨平台架构:在 Android、iOS 和 Web 上共享核心业务逻辑
- 高性能应用:优化资源使用和响应速度
- 可测试系统:支持高级调试和时间旅行测试
- 现代 UI 架构:无缝集成 Compose 和 Fragments
实际应用时根据项目需求混合使用这些模式,例如:
- 电商应用 :主界面用
SharedViewModel
,支付流程用SavedStateViewModel
- 社交媒体 :用户数据用
HiltViewModel
,实时消息用Flow
驱动 - 跨平台应用 :核心逻辑用
KMMViewModel
,平台特定功能用原生实现