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

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不会触发异常的漏洞,崩溃来自两个实现错误:

  1. 跨作用域查找NavController:在SecondFragment中通过Activity查找内部secondHostFragment对应的NavController,当SecondFragment被压入返回栈、视图销毁重建后,跨层级获取的NavController实例已和当前重建的NavHostFragment解绑,内部返回栈状态被清空,直接触发current destination为null的异常。2.3.5版本未对该状态做严格校验,因此不会崩溃,但该写法本身不符合Navigation的作用域设计规范。
  2. 重复绑定导航栏:在onStart生命周期中重复调用setupWithNavController,未随视图销毁解绑监听器,Fragment从返回栈恢复到前台时,失效的监听器持有旧的NavController引用,状态恢复或导航操作时就会抛出找不到目标目的地的异常。
  3. 重复设置导航图:代码中动态调用setGraph设置导航图,和xml中配置的navGraph属性冲突,会导致返回栈被意外重置。

修复方案

  1. 所有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)
}
  1. 将setupWithNavController绑定逻辑从onStart移动到onViewCreated中,和视图生命周期保持一致,避免重复绑定。
  2. 删除代码中动态调用setGraph的逻辑,直接在secondHostFragment的xml布局中配置app:navGraph="@navigation/second_nav_graph"即可,避免重复设置导航图清空返回栈。
  3. 主容器MainBottomNavFragment中的NavController初始化逻辑建议做同样修改,避免其他场景触发同类异常。

内容的提问来源于stack exchange,提问作者Milan Jiříček

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:33:23