Android 片段实例被保留,但子片段未重新附加
更新:接受的答案指向解释(bug)和解决方法,但也可以参见下面作为答案附加的基于Kotlin的解决方法 这段代码在Kotlin中,但我认为这是一个基本的android片段生命周期问题 我有一个片段包含对另一个“子片段”的引用 以下是我正在做的事情:Android 片段实例被保留,但子片段未重新附加,android,android-fragments,kotlin,Android,Android Fragments,Kotlin,更新:接受的答案指向解释(bug)和解决方法,但也可以参见下面作为答案附加的基于Kotlin的解决方法 这段代码在Kotlin中,但我认为这是一个基本的android片段生命周期问题 我有一个片段包含对另一个“子片段”的引用 以下是我正在做的事情: 我有一个主片段,retainInstance设置为true 我在主片段中有一个字段,它将保存对子片段的引用,最初这个字段为null 在主片段的onCreateView中,我检查子片段字段是否为null,如果为null,我创建子片段的实例并将其分配给该
retainInstance
设置为trueonCreateView
中,我检查子片段字段是否为null,如果为null,我创建子片段的实例并将其分配给该字段onPaused()
和onDestroyView()
方法,但是在将保留引用添加到子片段的过程中,我没有看到在子片段上调用任何生命周期方法,当重新创建主片段视图时,添加到子容器
净影响是我在主片段中没有看到子片段视图。如果我注释掉If(subfragment==null)并且每次只创建一个新的子片段,那么我会在视图中查看该子片段
更新
下面的答案确实指出了一个bug,在这个bug中,配置更改时不会保留childFragmentManager。这将最终打破我的预期用途,即在旋转后保留回撤,但我认为我看到的是不同的东西
我在activitiesonWindowFocusChanged
方法中添加了代码,当应用程序首次启动时,我看到类似的情况:
activity is in view
fm = FragmentManager{b13b9b18 in Tab1Fragment{b13b2b98}}
tab 1 fragments = [DefaultSubfragment{b13bb610 #0 id=0x7f0c0078}]
然后在旋转之后:
activity is in view
fm = FragmentManager{b13f9c30 in Tab1Fragment{b13b2b98}}
tab 1 fragments = null
这里fm是childFragmentManager,正如您所看到的,我们仍然有相同的Tab1Fragment实例,但它有一个新的childFragmentManager,我认为这是不需要的,这是由于下面的答案中报告的错误。
问题是,我确实将子片段添加到了这个新的子片段管理器中。
因此,事务似乎永远不会在引用保留的片段时执行,但如果我创建一个全新的片段,事务就会完成。(我尝试在新的childFragmentManager上调用executePendingTransactions
)
您的问题与此处描述的问题类似: 不幸的是,谷歌可能无法解决这个问题 将保留片段用于UI或嵌套片段不是一个好主意-建议使用它们来代替OnRetainOnConfiguration实例,例如用于大型集合/数据结构。此外,您可以找到比保留片段更好的加载程序,它们在配置更改期间也会保留
顺便说一句,我发现保留的片段更像是使用
android:configChanges
来“修复”屏幕旋转引起的问题。直到用户按下主屏幕,安卓决定终止你的应用程序进程,所有这些都会起作用。一旦用户想要回到你们的应用程序,你们保留的片段将被销毁,你们仍然需要重新创建它。因此,最好对所有内容进行编码,就像您的资源随时可能被破坏一样。对我上述问题的公认答案指出了支持库v4中报告的一个错误,其中嵌套片段(和子片段管理器)在配置更改时不再保留
其中一种方法提供了一种变通方法(似乎效果很好)。
解决方法包括创建Fragment的子类并使用反射
因为我最初的问题使用了Kotlin代码,所以我想我会在这里分享我的Kotlin版本的工作,以防其他人碰到这个问题。最后,我不确定我是否会坚持使用这个解决方案,因为它仍然是一种黑客行为,它仍然会操纵私有字段,但是如果字段名称发生更改,那么错误将在编译时而不是运行时发现
其工作方式如下:
我会仔细看看你发布的链接。我不是100%确定这是我遇到的同一个问题。顺便说一句,我真正想做的就是有几个标签,每个标签都有自己的背景。我见过很多涉及管理自己的后台堆栈的解决方案,但它们都有点陈旧,在大多数情况下,我认为嵌套片段似乎可以工作(每个选项卡都有自己的childFragmentManager和自己的后台堆栈),现在是处理设备旋转的关键部分。我现在不接受这个,我确实看到了错误,最终它将阻止我做我计划的事情,但这似乎是另一回事。我将补充我的问题我遇到的问题是r
class Tab1Fragment: Fragment() {
var subfragment: DefaultSubfragment? = null
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater!!.inflate(R.layout.fragment_main, container, false)
if (subfragment == null ) {
subfragment = DefaultSubfragment()
subfragment!!.sectionLabel = "label 1"
subfragment!!.buttonText = "button 1"
}
addRootContentToContainer(R.id.child_container, content = subfragment!!)
return rootView
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
inline fun Fragment.addRootContentToContainer(containerId: Int, content: Fragment) {
val transaction = childFragmentManager.beginTransaction()
transaction.replace(containerId, content)
transaction.commit()
}
// some convenience functions
inline fun Fragment.pushContentIntoContainer(containerId: Int, content: Fragment) {
val transaction = fragmentManager.beginTransaction()
transaction.replace(containerId, content)
transaction.addToBackStack("tag")
transaction.commit()
}
inline fun Fragment.addRootContentToContainer(containerId: Int, content: Fragment) {
val transaction = childFragmentManager.beginTransaction()
transaction.replace(containerId, content)
transaction.commit()
}
// here we address the bug
inline fun Fragment.reattachRetainedChildFragmentManager(childFragmentManager: FragmentManager) {
setChildFragmentManager(childFragmentManager)
updateChildFragmentsHost()
}
fun Fragment.setChildFragmentManager(childFragmentManager: FragmentManager) {
if (childFragmentManager is FragmentManagerImpl) {
mChildFragmentManager = childFragmentManager // mChildFragmentManager is private to Fragment, but the extension can touch it
}
}
fun Fragment.updateChildFragmentsHost() {
mChildFragmentManager.fragments.forEach { fragment -> // fragments is hidden in Fragment
fragment?.mHost = mHost // mHost is private also
}
}
class Tab1Fragment : Fragment() , TabRootFragment {
var subfragment: DefaultSubfragment? = null
var retainedChildFragmentManager: FragmentManager? = null
override val title = "Tab 1"
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater!!.inflate(R.layout.fragment_main, container, false)
if (subfragment == null ) {
subfragment = DefaultSubfragment()
subfragment!!.sectionLable = "label 1x"
subfragment!!.buttonText = "button 1"
addRootContentToContainer(R.id.child_container, content = subfragment!!)
}
return rootView
}
override fun onAttach(context: Context?) {
super.onAttach(context)
if (retainedChildFragmentManager != null) {
reattachRetainedChildFragmentManager(retainedChildFragmentManager!!)
} else {
retainedChildFragmentManager = childFragmentManager
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
}