Jetpack Compose 中的 ViewModel 作用域管理 —— 新手指南

一、 基本 ViewModel 使用

1.1 最简单的 ViewModel 获取

kotlin 复制代码
@Composable
fun SimpleScreen() {
    val viewModel: MyViewModel = viewModel()
    // 使用 viewModel
}

class MyViewModel : ViewModel() {
    private val _state = mutableStateOf(0)
    val state = _state.asStateFlow()
    
    fun increment() {
        _state.value++
    }
}

1.2 带参数的 ViewModel

kotlin 复制代码
@Composable
fun ScreenWithParam(userId: String) {
    val viewModel: UserViewModel = viewModel(
        factory = object : ViewModelProvider.Factory {
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return UserViewModel(userId) as T
            }
        }
    )
}

二、 ViewModel 作用域策略

2.1 不同层级的作用域

Activity 级作用域
kotlin 复制代码
@Composable
fun ActivityScopedViewModel() {
    val activity = LocalContext.current as ComponentActivity
    val viewModel: SharedViewModel = viewModel(
        viewModelStoreOwner = activity
    )
}
kotlin 复制代码
@Composable
fun NavGraphScopedViewModel(navController: NavHostController) {
    // 使用 navigation 图作为作用域
    val viewModel: GraphViewModel = hiltViewModel() // 使用 Hilt
    // 或者
    val viewModel: GraphViewModel = viewModel(
        viewModelStoreOwner = navController
            .currentBackStackEntryAsState().value
            ?.destination
            ?.parent
            ?.let { navController.getBackStackEntry(it.id) }
    )
}
Destination 级作用域(默认)
kotlin 复制代码
@Composable
fun ScreenWithViewModel() {
    // 默认作用域是当前的 Composable(通常是 NavDestination)
    val viewModel: ScreenViewModel = viewModel()
}

2.2 自定义作用域

kotlin 复制代码
// 创建自定义的 ViewModelStoreOwner
class CustomScope : ViewModelStoreOwner {
    private val viewModelStore = ViewModelStore()
    
    override fun getViewModelStore(): ViewModelStore = viewModelStore
    
    fun clear() {
        viewModelStore.clear()
    }
}

@Composable
fun rememberCustomScope(): CustomScope {
    return remember { CustomScope() }
}

@Composable
fun CustomScopedScreen() {
    val customScope = rememberCustomScope()
    val viewModel: CustomViewModel = viewModel(
        viewModelStoreOwner = customScope
    )
}

三、 高级作用域管理

kotlin 复制代码
// Navigation 设置
NavHost(navController, startDestination = "home") {
    navigation(startDestination = "list", route = "users") {
        composable("list") {
            // 这个 ViewModel 在 users 图内共享
            val sharedViewModel: UsersSharedViewModel = hiltViewModel()
            UserListScreen(sharedViewModel)
        }
        composable("detail/{userId}") {
            // 可以访问同一个 UsersSharedViewModel
            val sharedViewModel: UsersSharedViewModel = hiltViewModel()
            UserDetailScreen(sharedViewModel)
        }
    }
    
    composable("settings") {
        // 不同的作用域,不同的 ViewModel 实例
        val settingsViewModel: SettingsViewModel = hiltViewModel()
        SettingsScreen(settingsViewModel)
    }
}
kotlin 复制代码
@Composable
fun NestedNavigationExample() {
    val navController = rememberNavController()
    
    NavHost(navController, startDestination = "main") {
        navigation(
            startDestination = "dashboard",
            route = "main"
        ) {
            composable("dashboard") {
                // 作用域:main 图
                val mainViewModel: MainViewModel = hiltViewModel()
                DashboardScreen(mainViewModel)
            }
        }
        
        navigation(
            startDestination = "profile",
            route = "user"
        ) {
            composable("profile") {
                // 作用域:user 图(与 main 图隔离)
                val userViewModel: UserViewModel = hiltViewModel()
                ProfileScreen(userViewModel)
            }
        }
    }
}

四、 精确的生命周期控制

4.1 手动管理 ViewModel 生命周期

kotlin 复制代码
@Composable
fun ManualLifecycleControl() {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current
    
    // 使用 remember 和 DisposableEffect 精确控制
    val viewModel = remember {
        ViewModelProvider(
            ViewModelStore(),
            ViewModelProvider.NewInstanceFactory()
        ).get(ManualViewModel::class.java)
    }
    
    DisposableEffect(Unit) {
        // 连接生命周期
        lifecycleOwner.lifecycle.addObserver(viewModel)
        
        onDispose {
            // 清理资源
            lifecycleOwner.lifecycle.removeObserver(viewModel)
            viewModel.onCleared()
        }
    }
}

4.2 使用 ViewModel 清理策略

kotlin 复制代码
class ManagedViewModel(
    private val scope: CoroutineScope
) : ViewModel() {
    
    private val _uiState = MutableStateFlow(UiState())
    val uiState = _uiState.asStateFlow()
    
    private var job: Job? = null
    
    fun loadData() {
        job?.cancel()
        job = scope.launch {
            // 加载数据
            _uiState.value = UiState(loading = true)
            delay(1000)
            _uiState.value = UiState(data = "Loaded")
        }
    }
    
    override fun onCleared() {
        super.onCleared()
        job?.cancel()
        // 清理其他资源
    }
}

@Composable
fun rememberManagedViewModel(): ManagedViewModel {
    val scope = rememberCoroutineScope()
    val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {
        "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"
    }
    
    return remember {
        ViewModelProvider(
            viewModelStoreOwner,
            object : ViewModelProvider.Factory {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(modelClass: Class<T>): T {
                    return ManagedViewModel(scope) as T
                }
            }
        ).get(ManagedViewModel::class.java)
    }
}

五、 状态保存与恢复

5.1 使用 SavedStateHandle

kotlin 复制代码
class StatefulViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    companion object {
        private const val COUNTER_KEY = "counter"
        private const TEXT_KEY = "text"
    }
    
    var counter by savedStateHandle.saveable { mutableIntStateOf(0) }
        private set
    
    var text by savedStateHandle.saveable { mutableStateOf("") }
        private set
    
    fun increment() {
        counter++
    }
    
    fun updateText(newText: String) {
        text = newText
    }
}

@Composable
fun StatefulScreen() {
    val viewModel: StatefulViewModel = viewModel()
    // 状态会在配置更改时自动保存
}

5.2 自定义状态保存

kotlin 复制代码
class ComplexViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    private val _uiState = MutableStateFlow(ComplexState())
    val uiState = _uiState.asStateFlow()
    
    init {
        // 从 SavedStateHandle 恢复
        savedStateHandle.get<ComplexState>("complex_state")?.let {
            _uiState.value = it
        }
    }
    
    fun updateState(newState: ComplexState) {
        _uiState.value = newState
        // 可以在这里手动保存到 SavedStateHandle
    }
    
    @Suppress("FunctionName")
    fun SavedStateHandle.saveComplexState(state: ComplexState) {
        this["complex_state"] = state
    }
}

六、 最佳实践和模式

6.1 ViewModel 提供者模式

kotlin 复制代码
@Composable
inline fun <reified VM : ViewModel> scopedViewModel(
    scope: ViewModelScope = ViewModelScope.Screen,
    key: String? = null,
    factory: ViewModelProvider.Factory? = null
): VM {
    val owner = when (scope) {
        ViewModelScope.Activity -> {
            LocalContext.current as ComponentActivity
        }
        ViewModelScope.NavGraph -> {
            val navController = LocalNavController.current
            navController.currentBackStackEntryAsState().value
                ?.destination
                ?.parent
                ?.let { navController.getBackStackEntry(it.id) }
                ?: LocalViewModelStoreOwner.current
        }
        ViewModelScope.Screen -> LocalViewModelStoreOwner.current
        ViewModelScope.Custom -> {
            // 使用自定义作用域
            LocalCustomScope.current
        }
    }
    
    return viewModel(
        viewModelStoreOwner = checkNotNull(owner),
        key = key,
        factory = factory
    )
}

enum class ViewModelScope {
    Activity, NavGraph, Screen, Custom
}

6.2 依赖注入集成

kotlin 复制代码
// 使用 Hilt 进行依赖注入
@HiltViewModel
class InjectedViewModel @Inject constructor(
    private val repository: UserRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel 逻辑
}

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun UserScreen() {
    // Hilt 自动处理作用域和依赖
    val viewModel: InjectedViewModel = hiltViewModel()
}

七、 测试策略

7.1 测试不同作用域的 ViewModel

kotlin 复制代码
class ViewModelScopeTest {
    
    @Test
    fun testScreenScopedViewModel() = runTest {
        // 测试屏幕作用域的 ViewModel
        val owner = ViewModelStoreOwner { ViewModelStore() }
        val viewModel = TestViewModel(owner)
        
        // 验证行为
    }
    
    @Test
    fun testActivityScopedViewModel() = runTest {
        // 测试 Activity 作用域
        val activity = Robolectric.buildActivity(ComponentActivity::class.java).get()
        val viewModel = ViewModelProvider(activity).get(TestViewModel::class.java)
        
        // 验证跨屏幕的状态保持
    }
}

7.2 Mocking 作用域

kotlin 复制代码
@Composable
fun TestScreen(
    testViewModel: TestViewModel = mockViewModel()
) {
    // 在测试中注入 mock ViewModel
}

fun mockViewModel(): TestViewModel {
    return mockk<TestViewModel> {
        every { uiState } returns MutableStateFlow(TestUiState())
    }
}

总结

  1. 默认作用域 :在 Compose 中,viewModel() 默认使用当前 LocalViewModelStoreOwner,通常是 NavDestination。

  2. 作用域选择

    • 屏幕级:默认,适合单个屏幕
    • 导航图级:适合共享相关屏幕间的状态
    • Activity 级:适合全局共享状态
  3. 生命周期控制

    • 使用 DisposableEffect 进行精确控制
    • 利用 SavedStateHandle 进行状态持久化
    • onCleared() 中清理资源
  4. 最佳实践

    • 根据状态共享需求选择合适的作用域
    • 使用依赖注入(如 Hilt)简化管理
    • 为不同场景设计测试策略

通过合理使用 ViewModel 作用域,可以有效地管理状态的生命周期,避免内存泄漏,并确保状态在正确的上下文中共享和隔离。

相关推荐
独行soc11 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码203511 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_9151063211 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
vistaup11 小时前
OKHTTP 默认构建包含 android 4.4 的TLS 1.2 以及设备时间不对兼容
android·okhttp
常利兵11 小时前
ButterKnife在Android 35 + Gradle 8.+环境下的适配困境与现代化迁移指南
android
撩得Android一次心动11 小时前
Android LiveData 全面解析:使用Java构建响应式UI【源码篇】
android·java·android jetpack·livedata
熊猫钓鱼>_>12 小时前
移动端开发技术选型报告:三足鼎立时代的开发者指南(2026年2月)
android·人工智能·ios·app·鸿蒙·cpu·移动端
Rainman博1 天前
WMS-窗口relayout&FinishDrawing
android
baidu_247438611 天前
Android ViewModel定时任务
android·开发语言·javascript
有位神秘人1 天前
Android中Notification的使用详解
android·java·javascript