使用底部导航的Android Navigation组件不会销毁startDestination Fragment。

6
我已通过导航图(nav graph)以最基本的方式设置了底部导航(bottom navigation)。
NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)

当从被声明为startDestination的片段导航时,该片段永远不会被销毁(仅暂停),而所有其他片段在导航离开时都被销毁。

(我需要它被销毁,以便与之关联的viewModel中可以调用onCleared()回调函数)。

有任何想法吗?或如何更改此行为?

导航:

<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/navigation"
app:startDestination="@id/drawingFragment">

<fragment
    android:id="@+id/controllerFragment"
    android:name="com.example.android.myApp.ControllerFragment"
    android:label="fragment_controller"
    tools:layout="@layout/fragment_controller" >
    <action
        android:id="@+id/action_controllerFragment_to_drawingFragment"
        app:destination="@id/drawingFragment" />
</fragment>
<fragment
    android:id="@+id/drawingFragment"
    android:name="com.example.android.myApp.DrawingFragment"
    android:label="fragment_drawing"
    tools:layout="@layout/fragment_drawing" >
    <action
        android:id="@+id/action_drawingFragment_to_clippingFragment"
        app:destination="@id/clippingFragment"
        app:launchSingleTop="true"
        app:popUpTo="@+id/drawingFragment"
        app:popUpToInclusive="true" />
</fragment>
<fragment
    android:id="@+id/clippingFragment"
    android:name="com.example.android.myApp.ClippingFragment"
    android:label="fragment_clipping"
    tools:layout="@layout/fragment_clipping" />

主活动:

class MainActivity : AppCompatActivity() {

private lateinit var navHostFragment: NavHostFragment
private lateinit var bottomNavigationView: BottomNavigationView


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setUpNavigation()
}

fun setUpNavigation(){
    bottomNavigationView = findViewById(R.id.bttm_nav)
     navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

    NavigationUI.setupWithNavController(bottomNavigationView, navHostFragment.navController)}

activity_main/xml:

<?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"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/navigation" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bttm_nav"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemTextAppearanceActive="@style/bottomNaActive"
        app:itemTextAppearanceInactive="@style/bottomNavInactive"
        app:layout_constraintBottom_toBottomOf="@+id/nav_host_fragment"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/bottom_menu_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>

你能分享一下你的代码吗?如果可以的话。仅凭你提供的单行代码,我无法弄清楚问题所在。 - Akshay Sawant
3个回答

2

这实际上不是对@isrzanza答案的回答,而是一条评论(抱歉,声望不够)。

我假设在BottomNavigationView的情况下,没有“向上”或“向下”的概念。对于我来说,我可以从这里导航到的每个片段更像是“邻居”,或者同样重要,所以我认为不仅这些片段的ViewModel会被清除,而且我认为当导航“离开”时,片段本身也会通过onDestroy被销毁。

我不知道为什么特别是起始片段应该保留在内存中,而其他片段却不应该,因为它们同样重要,不是吗?


编辑:

我还想提到,我注意到如果我再次导航到startDestination-fragment,将创建一个相同类型的新片段(onCreate将再次执行),旧片段将被销毁(onDestroy将执行)。对我来说,这是资源的浪费。在这种情况下保留片段并在以后重新创建它是没有意义的。希望我在这里误解了什么 :)


1
这是一段确认 @isrzanza 答案的讲话。https://youtu.be/ELGShpd17wc?t=1320 - chrgue
作为一个可怕的黑客,似乎可以在NavController.OnDestinationChangedListener中获取startDestination片段并强制销毁。 - vitidev

0
你所描述的是导航组件的默认行为。当向下导航时,您从中导航的片段不会被销毁,只有当向上导航时才会销毁。
个人而言,我不理解为什么您想要通知viewModel该片段已被销毁,但如果您想在导航到另一个目标时运行特定代码,您可以在主活动(或片段中)使用NavController.OnDestinationChangedListener,并根据起始和结束目标执行某些操作。别忘了在销毁监听器时删除它。
如果您想无论如何销毁该片段,可以尝试更改导航图中导航操作的“弹出”参数。

0

@chrgue 正确描述了问题

在“顶层”导航的情况下,不存在updown的概念。但是startDestination片段被保留在内存中,并且在切换到它时重新创建。

如果应用程序仅包含“顶层”片段,则尤其不愉快。

我不知道如何正确解决这个问题。 对于我自己,我编写了下面的代码。

由于我不得不解决问题:返回按钮无法正常工作,在OOM后无法恢复(还有“弹出到”操作导航),因此代码变得复杂,这是一个可怕的黑客

扩展方法

fun FragmentActivity.enableDestroyStartDestination(
    navController: NavController,
    appBarConfiguration: AppBarConfiguration
) {
    val startDestinationId = navController.graph.startDestination

    var firstStart = true
    var preventMainRecursionFlag = false
    var latestDestionationIsMain = false
    navController.addOnDestinationChangedListener { controller, destination, args ->
        if (appBarConfiguration.topLevelDestinations.contains(destination.id)) {
            if (destination.id == startDestinationId) {
                latestDestionationIsMain = true
                if (firstStart) {
                    firstStart = false
                    return@addOnDestinationChangedListener
                }
                if (preventMainRecursionFlag) {
                    preventMainRecursionFlag = false
                    return@addOnDestinationChangedListener
                }
                preventMainRecursionFlag = true
                val options = NavOptions.Builder().setLaunchSingleTop(true).build()
                controller.navigate(startDestinationId, args, options)
            } else {
                val navHostFragment =
                    supportFragmentManager.primaryNavigationFragment as NavHostFragment
                if (navHostFragment.childFragmentManager.fragments.size > 0) {
                    if (latestDestionationIsMain) {
                        val fragment = navHostFragment.childFragmentManager.fragments[0]
                        navHostFragment.childFragmentManager.beginTransaction().remove(fragment)
                            .commitNowAllowingStateLoss()
                    }
                }
                latestDestionationIsMain = false
            }
        } else {
            latestDestionationIsMain = false
        }
    }
}

使用(导航抽屉或底部导航视图)

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    
        ...

        appBarConfiguration = AppBarConfiguration(
            setOf(...    ), drawerLayout
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)

        this.enableDestroyStartDestination(navController, appBarConfiguration)
    }

我不能保证它在任何地方都能正常工作 - 我的应用程序太简单了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接