Compose导航返回上一页时Lifecycle.Event.ON_CREATE重复触发问题
问题本质
Compose Navigation 默认实现中,当你从一个导航目的地跳转到其他页面时,上一个目的地的Composable会被完全移出视图树、关联的LifecycleOwner进入销毁流程;当执行返回操作回到该页面时,会重新走完Composable挂载、Lifecycle分发全流程,因此你监听的Lifecycle.Event.ON_CREATE会再次触发。
另外示例代码里存在导航逻辑错误:Blur页中点击按钮使用navController.navigate("home")不是返回操作,而是会在返回栈中新增一个Home页实例,进一步导致重复触发初始化逻辑。
修复步骤
- 修正返回导航逻辑,页面返回统一使用
popBackStack(),避免返回栈堆积重复页面实例:
// Blur页返回按钮逻辑修正 Button(onClick = { navController.popBackStack() }) { Text(text = "返回Home") }
- 一次性网络请求不要直接绑定在Composable的生命周期监听里,将数据请求、持有逻辑下沉到和导航目的地绑定的ViewModel中。ViewModel生命周期和返回栈中的目的地实例绑定,只要该目的地没有被弹出栈,返回页面时ViewModel会被复用,不会重新执行初始化请求:
// Home页对应ViewModel class HomeViewModel : ViewModel() { private val _listState = MutableStateFlow<List<ListItem>>(emptyList()) val listState: StateFlow<List<ListItem>> = _listState.asStateFlow() init { // 仅在ViewModel首次创建时执行一次网络请求 loadListData() } private fun loadListData() { Log.i("check","home 发起列表数据请求") // 实现网络请求逻辑,请求完成后更新_listState } }
在Home页composable中获取绑定的ViewModel实例即可:
composable("home") { // ViewModel和当前"home"目的地绑定,返回页面时不会重新创建 val homeViewModel: HomeViewModel = viewModel() val listData by homeViewModel.listState.collectAsStateWithLifecycle() Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { // 渲染列表数据 Button(onClick = { navController.navigate("blur") }) { Text(text = "进入Blur页") } } }
- 如果你确实需要在Composable内监听生命周期做触发控制,可以给自定义的生命周期监听方法增加首次执行标记,避免重复触发(注意:该方案仅适用于Composable未被完全销毁的场景,页面被dispose后remember存储的标记会被重置,无法替代ViewModel的生命周期持有能力):
@Composable fun RememberLifecycleEvent( targetEvent: Lifecycle.Event = Lifecycle.Event.ON_CREATE, onlyRunOnce: Boolean = true, onEvent: () -> Unit, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, ) { val currentEvent by rememberUpdatedState(newValue = onEvent) var hasExecuted by remember { mutableStateOf(false) } DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == targetEvent) { if (onlyRunOnce && hasExecuted) return@LifecycleEventObserver currentEvent() hasExecuted = true Log.i("check","触发事件: $event") } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } }
注意:不要试图通过修改Navigation源码、强制保活非前台页面的方式避免重组,会引发内存泄漏、状态错乱等不可预期问题,遵循数据层提升的单项数据流原则是最稳定的实现方案。
内容的提问来源于stack exchange,提问作者Haris iqbal
相关产品推荐
相关产品推荐

