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

Compose中使用DeepLinks后导航返回栈异常无法正常回退

问题根因

返回栈堆积重复实例是三个逻辑问题共同导致的:

  1. 中间页LaunchedEffect的key设置错误。你用statementsByYear == null这个布尔值作为触发key,第一次进入页面时数据为null,协程内的导航逻辑根本不会执行;等数据加载完成key变为false,协程重启执行导航后,只要后续重组导致这个布尔值波动,就会反复触发导航,重复压入MonthlyBillings实例。
  2. 从中间页跳转到目标页时没有配置栈清理规则,中间过渡页会一直残留在返回栈中。用户按返回键回到中间页后,会再次触发导航逻辑,循环叠加页面实例。
  3. 导航没有开启单实例模式,重复触发导航时不会复用栈内已有的目标页,只会持续创建新实例。
修改方案

1. 修复中间页的LaunchedEffect逻辑,增加防重复执行标记

不要用布尔值作为effect的key,直接以可观察的数据对象作为key,同时加执行标记避免重组重复触发导航:

@Composable
fun IntermediateMonthlyBillings(
    onDataAcquired: (AllStatementsByYear) -> Unit,
    myEwayLoggedInViewModel: MyEwayLoggedInViewModel = get()
) {
    val statementsByYear by myEwayLoggedInViewModel.statementsByYear.observeAsState(null)
    var hasTriggeredNavigation by remember { mutableStateOf(false) }

    if (statementsByYear == null) {
        GenericLoader(type = MyLoaderType.LIGHT_BACKGROUND)
    }
    LaunchedEffect(statementsByYear) {
        val data = statementsByYear
        if (data != null && !hasTriggeredNavigation) {
            hasTriggeredNavigation = true
            onDataAcquired(data)
        }
    }
}

2. 配置导航栈操作规则,跳转时移除中间页

从中间页导航到MonthlyBillings时,添加popUpTo规则把中间页从栈中完全移除,同时开启单实例模式避免重复创建目标页:

composable(Screens.INTERMEDIATE_MONTHLY_BILLINGS.navRoute,
    deepLinks = listOf(
        navDeepLink {
            uriPattern = "eway://traversals_monthly_bill_details"
        }
    )) {
    IntermediateMonthlyBillings({ statements ->
        val lastIndex = statements.lastIndex
        val gson = GsonBuilder().create()
        val statementsByYearJson = gson.toJson(statements)
        navController.navigate("${Screens.MONTHLY_BILLINGS}/$statementsByYearJson/$lastIndex") {
            popUpTo(Screens.INTERMEDIATE_MONTHLY_BILLINGS.navRoute) {
                inclusive = true // 弹出时包含中间页自身
            }
            launchSingleTop = true // 栈内已有目标页时直接复用,不新建实例
        }
    })
}

给MonthlyBillings的composable配置也加上单实例支持:

composable(
    route = "${Screens.MONTHLY_BILLINGS.navRoute}/{statementsByYear}/{initialIndex}",
    arguments = listOf(
        navArgument("statementsByYear") {},
        navArgument("initialIndex") {
            type = NavType.IntType
        }
    )
) {
    val gson = GsonBuilder().create()
    val statementsByYear = gson.fromJson(
        it.arguments?.getString("statementsByYear"),
        AllStatementsByYear::class.java
    )
    MonthlyBillings(statementsByYear, it.arguments?.getInt("initialIndex")!!)
    updateCurrentScreen(Screens.MONTHLY_BILLINGS)
}

3. 修正PendingIntent的flag适配系统要求

安卓12及以上版本要求必须显式声明PendingIntent的可变性,补充FLAG_IMMUTABLE标记即可,TaskStackBuilder自带的addNextIntentWithParentStack会自动按照导航图的层级关系构建标准返回栈,也就是你期望的ScreenA、ScreenC、ScreenF、MonthlyBillings结构,不需要额外调整:

TaskStackBuilder.create(this).run {
    addNextIntentWithParentStack(
        Intent(
            Intent.ACTION_VIEW,
            "eway://traversals_monthly_bill_details".toUri()
        )
    )
    getPendingIntent(
        2345,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
}
推荐优化(可选)

直接删掉IntermediateMonthlyBillings这个中间页,把deeplink直接绑定到MonthlyBillings的composable上,在MonthlyBillings页面内部监听ViewModel的数据状态:数据未加载时直接显示加载动画,加载完成后渲染页面内容。这种方式完全避免了中间页带来的栈管理问题,代码链路更短更稳定。


内容的提问来源于stack exchange,提问作者Stelios Papamichail

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 00:36:17