Jetpack Compose底部导航多回退栈下ViewModel被提前销毁如何解决?
问题根因
你当前的实现并没有真的实现每个Tab的独立回退栈,只是用了单NavController的状态保存恢复逻辑,这种方案只会保留每个Tab栈顶目的地的状态,切换Tab时,非栈顶的目的地关联实例会被销毁,这就是A2页面ViewModel被回收的原因。
解决方案
给每个Tab分配独立的NavController和NavHost,切换Tab时仅做内容的隐藏/显示,不会销毁非选中Tab的导航栈,自然就能保留栈内所有页面的ViewModel,直到你主动回退弹出对应页面。
实现步骤
- 首先定义底部Tab配置,存储每个Tab的独立NavController实例
sealed class BottomTab( val route: String, @StringRes val labelRes: Int, val icon: ImageVector ) { object TabA : BottomTab("tab_a", R.string.tab_a, Icons.Filled.Home) object TabB : BottomTab("tab_b", R.string.tab_b, Icons.Filled.Person) }
- 页面外层初始化Tab状态和对应NavController容器
val tabs = listOf(BottomTab.TabA, BottomTab.TabB) var selectedTab by rememberSaveable { mutableStateOf<BottomTab>(BottomTab.TabA) } // 存储每个Tab对应的NavController val tabNavControllers = remember { tabs.associateWith { mutableStateOf<NavController?>(null) } }
- 底部导航栏点击逻辑仅切换选中Tab,不再做全局导航跳转
Scaffold( bottomBar = { BottomNavigation { items.forEach { screen -> BottomNavigationItem( icon = { Icon(screen.icon, contentDescription = null) }, label = { Text(stringResource(screen.labelRes)) }, selected = selectedTab == screen, onClick = { selectedTab = screen } ) } } } ) { paddingValues -> Box(modifier = Modifier.padding(paddingValues)) { // 切换Tab时渲染对应Tab的独立导航图 Crossfade(targetState = selectedTab, label = "tab_switch") { tab -> val navController = rememberNavController() LaunchedEffect(navController) { tabNavControllers[tab]?.value = navController } NavHost( navController = navController, startDestination = if (tab == BottomTab.TabA) A1.route else B1.route ) { if (tab == BottomTab.TabA) { composable(A1.route) { val viewModelA1 = hiltViewModel() A1(viewModelA1) } composable(A2.route) { val viewModelA2 = hiltViewModel() A2(viewModelA2) } composable(A3.route) { val viewModelA3 = hiltViewModel() A3(viewModelA3) } } else { // 此处定义Tab B的导航逻辑 composable(B1.route) { val viewModelB1 = hiltViewModel() B1(viewModelB1) } } } } } }
- 适配返回键逻辑,优先回退当前选中Tab的导航栈
val backCallback = remember { object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { val currentNavController = tabNavControllers[selectedTab]?.value ?: return if (currentNavController.previousBackStackEntry != null) { currentNavController.popBackStack() } else { isEnabled = false finish() } } } } val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher LaunchedEffect(backDispatcher, backCallback) { backDispatcher?.addCallback(backCallback) }
补充说明
如果你不想调整现有架构,也可以在调用hiltViewModel()时指定作用域为当前Activity,这样ViewModel会在Activity销毁时才回收,但这种方案会导致内存占用升高,非特殊场景不推荐使用。
内容的提问来源于stack exchange,提问作者Cedar
相关产品推荐
相关产品推荐

