导航时片段生命周期重叠

17

我有一个只有一个Activity的应用程序,其中有多个Fragments通过导航组件进行切换。当我在两个fragment之间切换时,它们的onCreate()onDestroy()方法似乎会重叠。因此,当它们访问同一全局对象时,我很难为这些fragment编写初始化和清理代码。

Fragment_A导航到Fragment_B具有以下方法顺序:

Fragment_B.onCreate()
Fragment_A.onDestroy()

Fragment_A.onDestroy()中,我撤消了我在Fragment_A.onCreate()中所做的操作。当调用onCreate()时,我期望Fragment_B中的状态处于中立状态。但由于Fragment_A.onDestroy()尚未被调用,情况并非如此。

Android上是否正常重叠或者我在导航组件中配置错了?还有其他方法可以实现我想要做的事情吗?我知道我可以将两个Fragments耦合起来使其工作,但我不希望两个 Fragment 相互了解。对我来说,Fragment_B创建时Fragment_A仍然存在似乎很奇怪,因为Fragment_B应该替换Fragment_A

任何帮助都将不胜感激!


编辑:

在调试过程中查看源代码后,我发现在FragmentNavigator.navigate()中调用FragmentTransaction.setReorderingAllowed()允许重新排序操作,甚至允许在上一个片段的onDestroy()被调用之前调用新片段的onCreate()。问题仍然存在,如何解决在正确初始化下一个 Fragment 的全局状态之前正确清理上一个 Fragment 中的全局状态的问题。


1
@AADProgramming 它们可以为空,且它们的执行顺序保持不变。 - rozina
@rozina 如果你想一想,它们两个都需要活着才能使屏幕过渡无缝,否则在过渡期间可能会出现黑屏的情况?但这仍然没有回答问题,因为我和你一样(使用onPause&onResume处理媒体资源)。 - Blundell
没有代码只是猜测而已,但是:1)您是否在onCreate()中执行了最好在生命周期后面执行的操作(onResume() / onActivityCreated())?2)如果两个片段都在共同创建或清理某些内容,则它们之间存在一些耦合。这种创建和清理是否可以由父活动或片段来协调? - Juan
你的全局对象需要访问/清理什么?这个想法是否应该实现?两个片段需要通过活动相互通信,而不是相互依赖。如果您能在此处发布您的代码,那将非常好。 - Khoa Tran
在Fragment A中,您可以使用onPause(),而在Fragment B中,您可以使用onResume()吗? - Abdul Aziz
显示剩余2条评论
4个回答

8
Android中的Fragment生命周期并不是一个适当的回调主机,用于您的需求。导航控制器将以动画替换这两个片段,因此两个片段在某种程度上同时可见,甚至在进入片段的onResume()之后,退出片段的onPause()也会被调用。
解决方案1:使用OnDestinationChangedListener 在任何生命周期事件之前,都会调用onDestinationChanged()回调。作为一种非常简化的方法(注意泄漏),您可以执行以下操作:
    findNavController().addOnDestinationChangedListener { _, destination, _ ->
        if(shouldCleanupFor(destination)) cleanup()
    }

解决方案2:将全局更改抽象出来

不要让单个导航点更改全局状态,而是有一个单一的真相点。这可以是与导航层次无关的另一个片段。然后像以前一样观察导航:

findNavController(R.id.nav_graph).addOnDestinationChangedListener { _, destination, _ ->
    resetAll()
    when(distination.id) {
        R.id.fragment_a -> prepareForA()
        R.id.fragment_b -> prepareForB()
        else -> prepareDefault()
    }
}

作为额外的优势,您还可以实现状态更改的幂等性。

2
无论好坏,导航库在导航期间更改了片段的默认行为,因此您需要使用类似上面的解决方法或不使用导航库 - https://issuetracker.google.com/issues/155574963 - enyciaa
不要使用onViewCreated()和onDestroyView()来改变应用程序的行为,这种方法更好,也是更整洁的解决方案。 - alierdogan7

2
也许你可以将与初始化相关的代码移动到片段的onStart()onCreateView()方法中,在那里假定一个中性状态。根据开发者文档,这是初始化应该发生的地方。
另一个可用的选项是使用观察者/可观察模式,在片段A的onDestroy()完成后通知你的Activity。然后,Activity将通知片段B可以安全地假定已清理状态并开始初始化。

2

由于您有一个控制片段膨胀的活动,因此您可以手动控制正在膨胀的片段的生命周期。通过调用下面的方法,您可以控制哪个片段准备使用全局数据。此时,您必须通过某种方式将数据传递回MainActivity,以确定哪个片段处于活动状态,因为您正在询问如何同时膨胀两个共享对象的片段。更好的方法是让MainActivity实现FragmentA和FragmentB-detail,并使用特定类来处理操作,这样您就必须像平板电脑一样处理应用程序,并确定2个窗格模式,然后可以使用由Activity控制的这些片段中的适当类。包含的链接与您要完成的内容相匹配

private void addCenterFragments(Fragment fragment) {
        try {
            removeActiveCenterFragments();
            fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.add(R.id.content_fragment, fragment);
            fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
            activeCenterFragments.add(fragment);
            fragmentTransaction.commit();
        }catch (Exception e){
            Crashlytics.logException(e);
        }
    }

    private void removeActiveCenterFragments() {
        if (activeCenterFragments.size() > 0) {
            fragmentTransaction = fragmentManager.beginTransaction();
            for (Fragment activeFragment : activeCenterFragments) {
                fragmentTransaction.remove(activeFragment);
            }
            activeCenterFragments.clear();
            fragmentTransaction.commit();
        }
    }

2
我正在使用新的导航组件,它可以为我执行片段事务。否则,这是一种有效的方法。 - rozina

0

我的情况有点不同,我想分享一下,以防其他人遇到同样的问题。

我想在当前片段的 onPause() 中执行一个操作,但是当从一个片段导航到另一个片段时,不执行该代码。我需要做的是调用 isRemoving() 方法来检查当前片段是否正在被移除。当调用 NavController.navigate(...) 方法时,它会被设置为 true。

override fun onPause() {
    super.onPause()
    if (!isRemoving()) {
        // Write your code here
    }
}

根据Google的Fragment.isRemoving()文档:
返回true,如果此片段目前正在从其活动中被移除。这并不是指它的活动是否正在结束,而是指它正在被从其活动中移除的过程中。

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