Compose Navigation弹出路由后重编译查找路由崩溃问题咨询
问题解答
疑问1:页面多次重编译的原因
- 导航库版本问题:你使用的
androidx.navigation:navigation-compose:2.4.0-alpha09是早期测试版本,存在大量未优化的状态监听逻辑,会触发很多不必要的重组。 - NavController状态变更:导航操作、返回栈更新、页面生命周期状态变化都会触发NavHost及其子可组合项的重组。
- Compose固有重组逻辑:任何被读取的状态(比如Scaffold的padding值、导航状态)发生变更时,关联的可组合项都会触发重组。
疑问2:页面弹出后仍触发重编译的原因
2.4.0-alpha09版本存在已知bug:弹出页面时,被移除的NavBackStackEntry会先触发一次生命周期状态更新到STARTED,导致对应可组合项执行一次重组,之后才会被从返回栈彻底移除。你没有把ViewModel查找逻辑做缓存,每次重组都会执行getBackStackEntry,此时返回栈已经移除了对应路由,就会抛出异常。
疑问3:关于viewModel辅助方法的认知修正
你对该方法的认知有部分偏差:
lifecycle-viewmodel-compose提供的viewModel方法默认会取最近的ViewModelStoreOwner,在composable路由块内调用时,默认取当前路由对应的NavBackStackEntry作为存储载体,作用域仅为当前路由,并非限定到Activity/Fragment。- 但确实默认的
viewModel方法只能获取当前路由的ViewModel实例,无法直接获取父路由/导航子图的ViewModel实现跨页面共享。
疑问4:符合需求的最终解决方案
第一步:升级依赖
首先将navigation-compose升级到2.4.0稳定版及以上,该版本已经修复了弹出页面时不必要重组的bug,同时优化了返回栈状态监听逻辑。
implementation "androidx.navigation:navigation-compose:2.6.0"
你可以根据项目适配情况选择更高的稳定版本。
第二步:将ViewModel作用域绑定到导航子图
你不需要绑定到addresses页面,直接绑定到addressesModule导航子图的NavBackStackEntry上即可,整个子图内的所有页面都可以获取到同一个实例,子图被弹出时ViewModel会自动销毁,完全符合你的需求。
第三步:优化ViewModel查找逻辑,避免重复调用
将getBackStackEntry逻辑包在remember中,仅在当前页面首次组合时执行一次查找,后续重组不会重复执行,避免返回栈变更时抛出异常。
修复后的核心代码示例
优化后的scopedViewModel方法
@Composable inline fun <reified T : ViewModel> NavController.scopedViewModel( scopeRoute: String, currentBackStackEntry: NavBackStackEntry ): T { // 仅当前页面的BackStackEntry变化时才重新查找目标路由的BackStackEntry val viewModelStoreOwner = remember(currentBackStackEntry) { getBackStackEntry(scopeRoute) } return ViewModelProvider(viewModelStoreOwner)[T::class.java] }
路由块调用示例
navigation(route = addressesModule, startDestination = addresses) { composable(addresses) { backStackEntry -> // 作用域绑定到子图路由addressesModule val addressesViewModel: AddressesViewModel = navController.scopedViewModel(addressesModule, backStackEntry) Log.wtf( "NavSample", "Route: '$addresses', AddressesViewModel instance: $addressesViewModel", ) // 原有页面逻辑不变 } composable(route = addressDetails) { backStackEntry -> // 同一个子图路由,拿到的是同一个ViewModel实例 val addressesViewModel: AddressesViewModel = navController.scopedViewModel(addressesModule, backStackEntry) Log.wtf( "NavSample", "Route: '$addressDetails', AddressesViewModel instance: $addressesViewModel", ) // 原有页面逻辑不变 } }
经过以上修改后,不会再出现返回崩溃问题,ViewModel作用域也完全符合预期。
内容的提问来源于stack exchange,提问作者wujek
相关产品推荐
相关产品推荐

