理解Fragment的setRetainInstance(boolean)方法

366

首先,关于文档:

public void setRetainInstance (boolean retain)

控制片段实例是否在Activity重新创建(例如从配置更改)时保留。这仅适用于不在后退堆栈中的片段。如果设置了此属性,则当Activity被重新创建时,片段生命周期会略有不同:

  • onDestroy()不会被调用(但onDetach()仍会被调用,因为片段正在从其当前活动中分离)。
  • onCreate(Bundle)不会被调用,因为片段未被重新创建。
  • onAttach(Activity)和onActivityCreated(Bundle)仍将被调用。

我有一些问题:

  • 片段是否也保留其视图,还是会在配置更改时重新创建?“保留”具体意味着什么?

  • 用户离开活动时,片段会被销毁吗?

  • 为什么它不能与后退堆栈中的片段一起使用?

  • 哪些情况下使用这种方法是有意义的?


答案如下:
1. 片段的视图不受保留,会在配置更改时重新创建。保留指的是保留片段实例,以便在Activity重新创建时不会丢失其数据和状态。 2. 不会,只有当Activity被销毁时才会被销毁。 3. 这是因为后退堆栈中的片段可能需要在稍后的时间点进行恢复,而保留片段实例只能用于不在后退堆栈中的片段。 4. 当您有一个非常昂贵的片段(例如包含大量数据或需要长时间进行初始化)并且您希望在Activity重新创建时保留该片段的状态时,可以使用此方法。

5
类似的问题有很好的信息:为什么要使用Fragment#setRetainInstance(boolean)? - Richard Le Mesurier
1
多窗口(API 24)?这里的任何内容都不能被依赖。我发现Fragment.onDestroy()会间歇性地被调用,并且在Fragment中由setRetainInstance(true)保护的字段也会相应地被置空。 - Bad Loser
5个回答

367
首先,请查看我在保留片段方面的帖子,它可能会有所帮助。
现在来回答您的问题:
片段也会保留其视图状态,或者在配置更改时会重新创建 -“保留”到底是什么意思?
是的,片段的状态将在配置更改时被保留。具体来说,“保留”意味着即使配置更改导致基础活动被销毁,该片段也不会被销毁。
当用户离开活动时,片段会被销毁吗?
与Activity一样,当内存资源不足时,系统可能会销毁片段。无论您的片段是否在配置更改时保留其实例状态,都不影响系统在您离开Activity时是否销毁片段。如果您离开Activity(例如通过按下主屏幕按钮),则片段可能会被销毁或不会被销毁。如果您通过按下返回按钮离开Activity(从而调用finish()并有效地销毁Activity),则所有附加的Activity片段也将被销毁。
为什么不使用后退堆栈上的片段?可能有多个原因导致它不受支持,但对我来说最明显的原因是Activity 持有对 FragmentManager 的引用,而 FragmentManager 管理着后退栈。也就是说,在配置更改时,无论您选择保留还是不保留 FragmentActivity(以及 FragmentManager 的后退栈)都将被销毁。另一个可能导致其不能正常工作的原因是,如果允许保留和非保留片段同时存在于同一个后退栈中,则可能会变得棘手。

在哪些使用情况下使用此方法是有意义的?

Retained fragments 可以非常有用,用于传播状态信息 - 特别是线程管理 - 跨活动实例。例如,片段可以充当 ThreadAsyncTask 的实例的主机,管理其操作。请参阅我的博客文章了解更多信息。
一般来说,我会像使用 Activity 中的 onConfigurationChanged 一样处理它...不要仅仅因为您懒得正确实现/处理方向更改而使用它。只有在需要时才使用它。

43
视图对象不会被保留,它们总是在配置更改时被销毁。 - Markus Junginger
108
据我所知,如果您设置了setRetainInstance(true),那么Fragment的Java对象和其所有内容在旋转时不会被销毁,但视图会被重新创建。也就是说,onCreateView()方法会再次调用。这基本上是Android 1.0以来Activity应该工作的方式。我认为使用它并不是"懒惰"的行为,也不是不恰当的。事实上,我看不出为什么它不是默认设置,或者为什么您会想将其关闭。 - Timmmm
27
我发现你对于“为什么片段加入回退栈后不起作用?”的解释不太容易理解。不过可能是我太蠢了 :( - HGPB
14
一项活动可以以多种方式被销毁。例如,如果您点击“返回”,该活动将被销毁。如果您点击“主屏幕”,该活动将停止,并在某个时候被销毁(当内存不足时)。保留的 Fragment 仅在配置更改时保留,而基础活动被销毁并立即重新创建时。在活动被销毁的所有其他情况下,保留的片段也将被销毁。 - Alex Lockwood
3
@AlexLockwood,请确认以下内容:尽管使用了setRetainInstance(true),但仍需要实现自己的持久化(savedInstanceState或其他方式)才能处理所有情况,例如:“按下Home键、旋转、返回应用程序”会重新创建我的片段并调用构造函数,丢失所有状态变量。我有一个作为成员变量的AsyncTask,这就是我想要保留的原因,现在,如果我想让它工作,我被迫停止任务、保存状态,并在用户回来时恢复。所以总体来说,这只是一种快速处理旋转的方法,但一般情况下没什么用处。 - TWiStErRob
显示剩余24条评论

28

setRetaininstance只有在你的activity因配置更改而被销毁和重建时才有用,因为这些实例是在调用onRetainNonConfigurationInstance期间保存的。也就是说,如果你旋转设备,保留的片段将仍然存在(它们不会被销毁和重建),但当运行时杀死活动以回收资源时,什么也没有留下。当您按下返回按钮并退出活动时,所有内容都被销毁。

通常,我使用此功能来保存方向更改时间。假设我从服务器下载了一堆1MB的位图,当用户意外旋转设备时,我肯定不想再次执行所有下载工作。所以我创建一个包含我的位图的Fragment并将其添加到管理器中并调用setRetainInstance,即使屏幕方向发生变化,所有位图仍然存在。


你是否创建“仅数据”片段(没有任何小部件)只是为了保存位图,还是这些片段也可以有小部件?我读到过一些关于当片段包含与上下文/活动相关的内容时会产生内存泄漏危险的内容... - hgoebl
这个框架会为你清除 mActivity 引用。但我不知道运行时是否也会在这种情况下清除片段实例中的小部件。请尝试一下或深入源代码。 - suitianshi
setRetainInstance的使用示例很好。 - Mu Sa

13

setRetainInstance() - 已废弃

自Fragments版本1.3.0-alpha01起,setRetainInstance()方法已被废弃。随着ViewModel的引入,开发人员有了一个特定的API来保留可以与Activities,Fragments和Navigation graphs相关联的状态。这允许开发人员使用普通的非retained Fragment,并保持他们想要保留的特定状态分离,避免了常见的泄漏问题,同时还保留了单个创建和销毁保留状态的有用特性(即ViewModel的构造函数和它所接收的onCleared()回调)。


13

SetRetainInstance(true)允许片段在某种程度上生存下来。它的成员将在配置更改(如旋转)期间保留。但是,当包含的活动在后台被系统杀死时,它仍可能会被杀死。如果背景中的包含活动被系统杀死,则应该由系统保存其instanceState,并且您应该正确处理onSaveInstanceState。换句话说,始终会调用onSaveInstanceState。虽然如果SetRetainInstance为true且片段/活动尚未被杀死,则不会调用onCreateView,但如果它被杀死并尝试被重新启动,则仍将调用。

以下是android活动/片段的一些分析,希望对你有帮助。 http://ideaventure.blogspot.com.au/2014/01/android-activityfragment-life-cycle.html


8
旋转屏幕时,我确实看到保留的fragment再次调用了onCreateView方法。 - aij
这个链接是你自己的博客吗?如果是的话,你应该明确说明。 - Flexo

2

setRetainInstance(boolean)方法在你想要一个不受Activity生命周期控制的组件时非常有用。例如,在rxloader中使用此技术,来“处理Android的活动生命周期以适应rxjava的Observable”(我在这里发现了它)。


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