如何使用setRetainInstance(true)和将Fragments添加到backstack中来破坏事物?

24

setRetainInstance文档中提到:

这只能用于不在后退栈中的片段。

所以我开始尝试它。

我有一个Activity,先添加了A片段。

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.content, new PackageFragment());
ft.commit

然后从这个fragment中,我运行了来自父Activity的一个方法,将fragment B添加到返回栈中。

FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.content, new OrderFragment());
ft.addToBackStack(null);
ft.commit();

然后我从onCreate、onDestroy、onSaveInstanceState、onActivityCreated等中创建日志消息。

我尝试了这个过程的两个版本。在每个片段上旋转设备。

  1. 默认

一切都如预期。片段上的onCreate、onDestroy被触发。

  1. setRetainInstance(true)

一切都如预期吗?片段上的onCreate、onDestroy不会被触发。

而且当片段在返回堆栈中时,似乎一切正常...那么为什么文档说我不应该使用它呢? 我可能会遇到哪些问题?

谢谢


当您按下返回按钮时,才会开始看到差异。例如,如果您在显示临时详细信息视图片段上使用了setRetainInstance,那么按下返回按钮可能会将您带出应用程序,而不是关闭此临时详细信息视图片段。 - Marco RS
谢谢Marco,你能重现这个问题吗?如果可以,请分享一下。我可以轻松地按回退键。我测试了几种情况,当我按回退键时,无论两个片段是否都设置了setRetainInstance(true),详细信息片段始终会被正确销毁。 - AndroidGecko
我撤回我的评论。我看到了类似的行为... - Marco RS
1个回答

26

更新的回答:

我可能会遇到哪些麻烦情况?

在将Fragment添加到返回栈并从onSaveInstanceState()中传递Bundle到配置更改时,调用setRetainInstance(true)将使Bundle在配置更改时为null。

(我不确定开发人员是否会尝试这样做,因为使用setRetainInstance(true)使onSaveInstanceState()有点多余,但我没有在API文档中看到这种行为,所以我写了这个答案)。

如果同时调用addToBackStack()setRetainInstance(true),则与仅调用addToBackStack()相比,在配置更改时setRetainInstance()部分更改了Fragment生命周期方法的调用和参数值。

具体而言,在下面的测试中,查看仅调用addToBackStack()和同时调用setRetainInstance(true)会发生什么,并在配置更改时观察结果:

仅调用addToBackStack()而不调用setRetainInstance(true)

  • 调用onCreate()onDestroy()
  • onSaveInstanceState()传递的一个bundle作为参数在onCreateView()中接收。

同时调用addToBackStack()setRetainInstance(true)

  • 不会调用onCreate()onDestroy()。这在API文档中有提到。
  • 不会在onCreateView()中接收从onSaveInstanceState()传递的bundle。传入的Bundle为null。

进行了记录方法调用和测试参数是否为null的测试:

Activity中:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    MyFragment fragment;
    if (savedInstanceState != null) {
        fragment = (MyFragment) getFragmentManager().findFragmentByTag("my_fragment_tag");
    } else {
        fragment = new MyFragment();
        FragmentTransaction t = getFragmentManager().beginTransaction();
        t.addToBackStack(null);//toggle this
        t.add(android.R.id.content, fragment, "my_fragment_tag").commit(); 
    }
}

Fragment 中:
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);//toggle this
}

并且

@Override
public void onSaveInstanceState(Bundle outState) {
    outState.putString("test", "value");
    super.onSaveInstanceState(outState);
}

测试1:当调用addToBackStack(),且未调用setRetainInstance(true)时,Fragment的生命周期如下:

  • onAttach()
  • onCreate()
  • onCreateView()
  • onActivityCreated()
  • onStart()
  • onResume()

[设备从竖屏旋转到横屏]

  • onPause()
  • onSaveInstanceState()
  • onStop()
  • onDestroyView()
  • onDestroy()
  • onDetach()
  • onAttach()
  • onCreate()
  • onCreateView()(带有非空bundle参数)
  • onStart()
  • onResume()

测试2和3:调用setRetainInstance(true)并调用/不调用addToBackStack()时,Fragment的生命周期如下(结果相同):

  • onAttach()
  • onCreateView()
  • onActivityCreated()
  • onStart()
  • onResume()

[设备从竖屏旋转到横屏]

  • onPause()
  • onSaveInstanceState()
  • onStop()
  • onDestroyView()
  • onDetach()
  • onAttach()
  • onCreateView()(带有空的bundle参数)
  • onStart()
  • onResume()

1
“将Fragment添加到backstack会导致此生命周期,您有参考资料吗?查看代码,我没有看到onAttach()的调用是否取决于Fragment是否要添加到back stack。谢谢!” - CommonsWare
谢谢您指出这一点。我的意思是在向后导航时,而不仅仅是添加片段时。我已经编辑了句子,以更清楚地表达我的意思。这是我使用的参考资料:http://assets.en.oreilly.com/1/event/68/Fragments%20for%20All%20Presentation.pdf。第19节:“如果您在删除或替换片段时调用addToBackStack():(...)如果用户导航回,则系统会在片段上调用onCreateView()、onActivityCreated()、onStart()和onResume()。” - Gunnar Karlsson
另一个参考资料是Reto Meier的《Professional Android 4 Application Development》,图4.9(Fragment生命周期摘要):“Fragment.onCreateView()(...)Fragment从返回堆栈中返回到布局”。 - Gunnar Karlsson
1
你引用的API文档是特别针对在配置更改后重新创建活动时的情况。更具体地说,如果活动尚未重新创建,则在导航回存储在后退堆栈中的已删除片段时,即使已调用setRetainInstance(true),也不会调用onAttach()。当在后退堆栈上的片段上重新创建活动时,非保留片段将立即接收到onAttach(),而保留片段只有在再次导航到它们时才会接收到onAttach()。 - antonyt
@antonyt 感谢您的深入评论。我已经发布了更新的答案。 - Gunnar Karlsson
1
@GunnarKarlsson 我看到 savedInstanceState != null 即使在 onCreateView() 中设置了 setRetainedInstance(true)。也许这种行为自你上次发布以来已经改变了。有人可以确认吗? - Saqib

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