You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose底部导航多回退栈下ViewModel被提前销毁如何解决?

问题根因

你当前的实现并没有真的实现每个Tab的独立回退栈,只是用了单NavController的状态保存恢复逻辑,这种方案只会保留每个Tab栈顶目的地的状态,切换Tab时,非栈顶的目的地关联实例会被销毁,这就是A2页面ViewModel被回收的原因。

解决方案

给每个Tab分配独立的NavController和NavHost,切换Tab时仅做内容的隐藏/显示,不会销毁非选中Tab的导航栈,自然就能保留栈内所有页面的ViewModel,直到你主动回退弹出对应页面。

实现步骤

  1. 首先定义底部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)
}
  1. 页面外层初始化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)
    }
}
  1. 底部导航栏点击逻辑仅切换选中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)
                    }
                }
            }
        }
    }
}
  1. 适配返回键逻辑,优先回退当前选中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 23:09:03