Jetpack Navigation 2.4+多返回栈下嵌套BottomNavigationView崩溃问题
问题描述
将Navigation组件从2.3.5版本升级到2.4.2版本后,触发崩溃报错:java.lang.IllegalArgumentException: No destination with ID XXX is on the NavController's back stack. The current destination is null
复现路径:FirstFragment → 携带独立BottomNavigationView的SecondFragment → ThirdFragment → 返回SecondFragment时触发崩溃。
现有实现逻辑
项目中存在两个搭载BottomNavigationView的Fragment容器:
- 主底部导航容器MainBottomNavFragment
对应Fragment代码:
class MainBottomNavFragment : Fragment() { private lateinit var navController: NavController override fun onStart() { super.onStart() if (!::navController.isInitialized) { navController = Navigation.findNavController(requireActivity(), R.id.mainBottomNavHostFragment) } binding.bottomNavigation.setupWithNavController(navController) } }
对应布局代码:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/mainBottomNavHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:defaultNavHost="false" app:layout_constraintBottom_toTopOf="@+id/bottomNavigation" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/main_menu_nav_graph" /> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottomNavigation" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:menu="@menu/main_menu" /> </androidx.constraintlayout.widget.ConstraintLayout>
主导航图main_menu_nav_graph配置:
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/main_bottom_nav_graph" app:startDestination="@id/first"> <fragment android:id="@+id/first" android:name=".FirstFragment" android:label="FirstFragment" /> <fragment android:id="@+id/second" android:name=".SecondFragment" android:label="SecondFragment" /> <fragment android:id="@+id/third" android:name=".ThirdFragment" android:label="ThirdFragment"> <action ... /> </fragment> <navigation android:id="@+id/fourth" app:startDestination="@id/fourthFragment"> <fragment android:id="@+id/fourthFragment" android:name=".FourthFragment" android:label="FourthFragment" /> <fragment android:id="@+id/fourthFragmentA" android:name=".FourthFragmentA" android:label="FourthFragmentA" /> <fragment android:id="@+id/fourthFragmentB" android:name=".FourthFragmentB" android:label="FourthFragmentB" /> </navigation> </navigation>
- 二级底部导航容器SecondFragment
对应Fragment代码:
class SecondFragment : Fragment() { private lateinit var navController: NavController override fun onStart() { super.onStart() if (!::navController.isInitialized) { navController = Navigation.findNavController(requireActivity(), R.id.secondHostFragment) val inflater = navController.navInflater navController.setGraph(inflater.inflate(R.navigation.second_nav_graph), arguments) } binding.secondBottomNavigation.setupWithNavController(navController) } }
对应布局代码:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/secondBottomNavigation" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:menu="@menu/second_menu" /> <fragment android:id="@+id/secondHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/secondBottomNavigation" /> </androidx.constraintlayout.widget.ConstraintLayout>
二级导航图second_nav_graph配置:
<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/second_nav_graph" app:startDestination="@id/secondA"> <fragment android:id="@+id/secondA" android:name=".SecondA" android:label="SecondA"> <action ... /> </fragment> <fragment android:id="@+id/secondB" android:name=".SecondB" android:label="SecondB" /> </navigation>
问题解答
双层嵌套BottomNavigationView是否支持?
支持,但必须遵循Navigation组件的作用域规则,每个NavHostFragment必须和对应的NavController绑定在正确的生命周期作用域内,不能跨作用域查找、持有NavController实例。
版本升级后崩溃原因
2.4.0及以上版本Navigation组件收紧了NavController状态校验逻辑,修复了旧版本中跨作用域持有NavController不会触发异常的漏洞,崩溃来自两个实现错误:
- 跨作用域查找NavController:在SecondFragment中通过Activity查找内部secondHostFragment对应的NavController,当SecondFragment被压入返回栈、视图销毁重建后,跨层级获取的NavController实例已和当前重建的NavHostFragment解绑,内部返回栈状态被清空,直接触发current destination为null的异常。2.3.5版本未对该状态做严格校验,因此不会崩溃,但该写法本身不符合Navigation的作用域设计规范。
- 重复绑定导航栏:在
onStart生命周期中重复调用setupWithNavController,未随视图销毁解绑监听器,Fragment从返回栈恢复到前台时,失效的监听器持有旧的NavController引用,状态恢复或导航操作时就会抛出找不到目标目的地的异常。 - 重复设置导航图:代码中动态调用
setGraph设置导航图,和xml中配置的navGraph属性冲突,会导致返回栈被意外重置。
修复方案
- 所有Fragment内部嵌套的NavHostFragment,不要从Activity层级查找NavController,直接从当前Fragment的childFragmentManager获取实例:
// SecondFragment中替换原有初始化逻辑 override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val navHostFragment = childFragmentManager.findFragmentById(R.id.secondHostFragment) as NavHostFragment navController = navHostFragment.navController // 仅在视图创建时绑定一次导航栏 binding.secondBottomNavigation.setupWithNavController(navController) }
- 将
setupWithNavController绑定逻辑从onStart移动到onViewCreated中,和视图生命周期保持一致,避免重复绑定。 - 删除代码中动态调用
setGraph的逻辑,直接在secondHostFragment的xml布局中配置app:navGraph="@navigation/second_nav_graph"即可,避免重复设置导航图清空返回栈。 - 主容器MainBottomNavFragment中的NavController初始化逻辑建议做同样修改,避免其他场景触发同类异常。
内容的提问来源于stack exchange,提问作者Milan Jiříček
相关产品推荐
相关产品推荐

