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

解决BottomNavigationView重复创建Fragment引发的TransactionTooLargeException问题

问题描述

我有一个带5个菜单项的BottomNavigationView,偶尔会触发android.os.TransactionTooLargeException错误。用TooLargeTool排查后发现,多次点击标签页再把应用切到后台时会触发这个异常——每次点击都会创建新的Fragment实例,最终导致Parcel序列化的数据大小超过1MB限制。

相关代码

主Activity代码

val navController = findNavController(R.id.nav_host_fragment_activity_main)

WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = false

navController.addOnDestinationChangedListener { _, destination, _ ->
    when (destination.id){
        R.id.rifleChoiceFragment -> {
            navView.visibility = View.GONE
            window.statusBarColor = defaultColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
        }
        R.id.ammoChoiceFragment -> {
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            navView.visibility = View.GONE
            window.statusBarColor = defaultColor
        }
        R.id.dataBaseFragment-> {
            navView.visibility = View.GONE
            window.statusBarColor = defaultColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
        }
        R.id.caliberChoiceFragment-> {
            navView.visibility = View.GONE
            window.statusBarColor = defaultColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
        }
        R.id.vendorChoiceFragment-> {
            navView.visibility = View.GONE
            window.statusBarColor = defaultColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
        }
        R.id.secondAmmoChoiceFragment-> {
            navView.visibility = View.GONE
            window.statusBarColor = defaultColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
        }
        R.id.payWallFragment -> {
            navView.visibility = View.GONE
            window.statusBarColor = blackColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = false
        }
        R.id.splashFragment-> {
            navView.visibility = View.GONE
            window.statusBarColor = blackColor
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = false
        }
        else -> {
            WindowInsetsControllerCompat(window,window.decorView).isAppearanceLightStatusBars = true
            navView.visibility = View.VISIBLE
            window.statusBarColor = defaultColor
        }
    }
}
navView.setupWithNavController(navController)

导航图(mobile_navigation.xml)

<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/splashFragment">

<fragment
    android:id="@+id/navigation_home"
    android:name="NAME"
    android:label="@string/title_home"
    tools:layout="@layout/fragment_ammo" >
    <action
        android:id="@+id/action_navigation_home_to_rifleChoiceFragment"
        app:destination="@id/rifleChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_navigation_home_to_ammoChoiceFragment"
        app:destination="@id/ammoChoiceFragment"
        app:launchSingleTop="false" />
</fragment>

<fragment
    android:id="@+id/navigation_dashboard"
    android:name="NAME"
    android:label="@string/title_dashboard"
    tools:layout="@layout/fragment_table" />

<fragment
    android:id="@+id/navigation_notifications"
    android:name="NAME"
    android:label="@string/Trajectory"
    tools:layout="@layout/fragment_plot" >
    <action
        android:id="@+id/action_navigation_notifications_to_secondAmmoChoiceFragment"
        app:destination="@id/secondAmmoChoiceFragment"
        app:launchSingleTop="false" />
</fragment>
<fragment
    android:id="@+id/navigation_target"
    android:name="NAME"
    android:label="@string/reticle"
    tools:layout="@layout/fragment_target" >
    <action
        android:id="@+id/action_navigation_target_to_reticleChoiceFragment"
        app:destination="@id/reticleChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_navigation_target_to_targetChoiceFragment"
        app:destination="@id/targetChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_navigation_target_to_payWallFragment"
        app:destination="@id/payWallFragment"
        app:launchSingleTop="true" />
</fragment>
<fragment
    android:id="@+id/navigation_settings"
    android:name="NAME t"
    android:label="@string/settings"
    tools:layout="@layout/settings_fragment" />
<fragment
    android:id="@+id/rifleChoiceFragment"
    android:name="NAME"
    android:label="rifle_choice_fragment"
    tools:layout="@layout/rifle_choice_fragment" />
<fragment
    android:id="@+id/ammoChoiceFragment"
    android:name="NAME"
    android:label="fragment_ammo_choice"
    tools:layout="@layout/fragment_ammo_choice" >
    <action
        android:id="@+id/action_ammoChoiceFragment_to_dataBaseFragment"
        app:destination="@id/dataBaseFragment"
        app:launchSingleTop="false" />
</fragment>
<fragment
    android:id="@+id/dataBaseFragment"
    android:name="NAME"
    android:label="fragment_data_base"
    tools:layout="@layout/fragment_data_base" >
    <action
        android:id="@+id/action_dataBaseFragment_to_caliberChoiceFragment"
        app:destination="@id/caliberChoiceFragment"
        app:launchSingleTop="false" />
    <action
        android:id="@+id/action_dataBaseFragment_to_vendorChoiceFragment"
        app:destination="@id/vendorChoiceFragment"
        app:launchSingleTop="false" />
</fragment>
<fragment
    android:id="@+id/caliberChoiceFragment"
    android:name="NAME"
    android:label="fragment_caliber_choice"
    tools:layout="@layout/fragment_caliber_choice" />
<fragment
    android:id="@+id/vendorChoiceFragment"
    android:name="NAME"
    android:label="fragment_vendor_choice"
    tools:layout="@layout/fragment_vendor_choice" />
<fragment
    android:id="@+id/secondAmmoChoiceFragment"
    android:name="NAME"
    android:label="fragment_second_ammo_choice"
    tools:layout="@layout/fragment_second_ammo_choice" >
    <action
        android:id="@+id/action_secondAmmoChoiceFragment_to_dataBaseFragment"
        app:destination="@id/dataBaseFragment" />
</fragment>
<fragment
    android:id="@+id/reticleChoiceFragment"
    android:name="NAME"
    android:label="fragment_reticle_choice"
    tools:layout="@layout/fragment_reticle_choice" />
<fragment
    android:id="@+id/targetChoiceFragment"
    android:name="NAME"
    android:label="fragment_target_choice"
    tools:layout="@layout/fragment_target_choice" />
<fragment
    android:id="@+id/payWallFragment"
    android:name="NAME"
    android:label="fragment_pay_wall"
    tools:layout="@layout/fragment_pay_wall" >
    <action
        android:id="@+id/action_payWallFragment_to_navigation_home"
        app:destination="@id/navigation_home"
        app:launchSingleTop="false"
        app:popUpTo="@id/mobile_navigation"
        app:popUpToInclusive="true" />
</fragment>
<fragment
    android:id="@+id/splashFragment"
    android:name="NAME"
    android:label="fragment_splash"
    tools:layout="@layout/fragment_splash" >
    <action
        android:id="@+id/action_splashFragment_to_navigation_home"
        app:destination="@id/navigation_home"
        app:launchSingleTop="false"
        app:popUpTo="@id/mobile_navigation"
        app:popUpToInclusive="true" />
    <action
        android:id="@+id/action_splashFragment_to_payWallFragment"
        app:destination="@id/payWallFragment"
        app:launchSingleTop="false"
        app:popUpTo="@id/mobile_navigation"
        app:popUpToInclusive="true" />
</fragment>

</navigation>

底部导航菜单(menu.xml)

<?xml version="1.0" encoding="utf-8"?>
<item
    android:id="@+id/navigation_home"
    android:icon="@drawable/ic_bulletsvg"
    android:title="@string/title_home" />

<item
    android:id="@+id/navigation_dashboard"
    android:icon="@drawable/ic_spreadsheet"
    android:title="@string/title_dashboard"
   />

<item
    android:id="@+id/navigation_notifications"
    android:icon="@drawable/ic_b"
    android:title="@string/Trajectory"
    />
<item
    android:id="@+id/navigation_targ"
    android:icon="@drawable/ic_ret"
    android:title="@string/ret"
    />

<item
    android:id="@+id/navigation_settings"
    android:icon="@drawable/ic_set"
    android:title="@string/settings"
    />

问题: 正确的修复方式是什么?是否需要手动处理每个标签页的点击事件?


解决方案

不需要手动处理每个标签页的点击事件,利用Navigation组件的内置配置就能解决问题,核心是避免重复创建底部导航对应的Fragment实例,同时控制返回栈的大小。

1. 给底部导航Fragment配置launchSingleTop="true"

在导航图中,给所有底部导航对应的Fragment(navigation_home、navigation_dashboard、navigation_notifications、navigation_target、navigation_settings)添加app:launchSingleTop="true"属性。这样点击已处于栈顶的标签时不会创建新实例;切换其他标签时会复用已存在的实例,而非新建。

修改后的示例:

<fragment
    android:id="@+id/navigation_home"
    android:name="NAME"
    android:label="@string/title_home"
    tools:layout="@layout/fragment_ammo"
    app:launchSingleTop="true">
    <!-- 保留原有action配置 -->
</fragment>

2. 配置popUpTo控制返回栈深度

为防止返回栈中积累过多Fragment实例,需要在底部导航跳转时设置返回栈清理规则。可以通过代码实现:
移除原有的navView.setupWithNavController(navController),替换为自定义选中监听器,利用NavOptions配置跳转逻辑:

navView.setOnItemSelectedListener { item ->
    val navOptions = NavOptions.Builder()
        .setLaunchSingleTop(true)
        // 切换底部标签时,将返回栈弹出到起始页,只保留当前Fragment
        .setPopUpTo(navController.graph.startDestinationId, false)
        .build()
    navController.navigate(item.itemId, null, navOptions)
    true
}

3. 优化Fragment状态保存

检查每个Fragment的onSaveInstanceState方法,避免保存过大的数据(如大量列表数据、Bitmap等)。如果必须保存,优先将数据存储到ViewModel或本地数据库,而非通过Bundle传递,减少Parcel的序列化大小。

4. 修正底部菜单与导航图的ID匹配问题

注意到底部菜单中的navigation_targ与导航图中的navigation_targetID不匹配,会导致导航异常,需将底部菜单的ID修正为navigation_target,确保点击能正确对应到导航图中的Fragment。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 11:50:45