使用 NavigationUI 切换回片段时调用 OnDestory()

OnDestory() being called when switching back to fragment with NavigationUI

我正在使用带有底部导航视图的 NavigationUI,并且还使用操作栏对其进行了设置。

我的应用程序从主页片段开始,当我切换到我的搜索片段时 OnDestroyView()homeFragment 上被调用(预期)但是当我从搜索片段切换回我的主页片段时onDestroy() 被调用,然后显示 homefragment 的一个新实例,而不是它恢复到我离开时的状态的那个。我如何让它恢复到之前的状态?

这是我设置 NavigationUI 的方式:

NavController navController = Navigation.findNavController(this,
         R.id.navigation_host_fragment);

NavigationUI.setupWithNavController(mainBinding.mainActivityBn, navController);
NavigationUI.setupActionBarWithNavController(this,navController);

我的导航图:

<?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/main_activity_navigation"
app:startDestination="@id/homeFragment">

  <fragment
    android:id="@+id/homeFragment"
    android:name=<name>
    android:label="Home"
    tools:layout="@layout/fragment_home" />

  <fragment
    android:id="@+id/serachFragment"
    android:name=<name>
    android:label="Search"
    tools:layout="@layout/fragment_search" />

 </navigation>

编辑: 我正在使用底部导航视图在片段之间导航。当我按下后退按钮时,主页片段将按照之前的状态加载(预期行为)。

根据this issue

To understand the logic here, we need to look at what NavigationUI does when you select a destination. Looking at the source code and removing the unrelated code (animations, secondary menu code):

NavOptions.Builder builder = new NavOptions.Builder()
  .setLaunchSingleTop(true);
builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
NavOptions options = builder.build();
navController.navigate(item.getItemId(), null, options);

So the popUpTo pops up to the root destination, but uses false to not pop the root destination itself. It then uses setLaunchSingleTop(true) to avoid having two instances of the start destination on the back stack. The intent here is to not clear any ViewModel or SavedStateHandle associated with that root destination when you go back to it.

So NavController calls navigate(), we pop everything up to but not including the start destination (your first screen) off the back stack, correctly removing the second screen.

NavController then calls through to FragmentNavigator to do the navigate() call. FragmentNavigator sees the launchSingleTop is true and correctly determines that the fragment is already on the top of the stack. However, Fragments, unlike activities, do not have any equivalent to onNewIntent() for onNewArguments() for mimicking the singleTop behavior, so FragmentNavigator pops the previous fragment and adds a new instance of the Fragment, passing in the new arguments (here, just null).

因此,由于 Fragment 处理 setLaunchSingleTop(true) 标志的方式,单击底部导航栏中的第一项会再次破坏并重新创建 Fragment。这就是系统后退按钮行为不同的原因 - 系统后退按钮只是调用 popBackStack() 而不是使用任何单一顶部行为。

该问题仍未解决,表明:

The behavior of FragmentNavigator with single top is bad and should feel bad. It does not match the behavior of activities and is unintuitive. If you don't mind, let's use this bug to track that work. Ideally, a new instance would not be created, meaning your original MapFragment would just be resumed and would be right back where it was. This would be consistent with what happens when you hit the system back button.

该问题没有直接的解决方法,因此最好的结果(不发生这种情况)需要您对该问题加注星标并等待它得到修复。但是,您会注意到,您保存到 NavBackStackEntry 中的任何状态或 ViewModel 都将 通过 singleTop 操作保存:

// Instead of using this:
myViewModel = new ViewModelProvider(this).get(MyViewModel.class);

// You could use:
NavController navController = NavHostFragment.findNavController(this);
NavBackStackEntry entry = navController.getBackStackEntry(R.id.homeFragment);
myViewModel = new ViewModelProvider(entry).get(MyViewModel.class);

但是,这对保存 Fragment 的视图状态(即滚动位置)没有帮助,因为它们无法保存到 NavBackStackEntry 中,而只是 在片段级别保存。