FragmentManager 使用 Tips
-
在 activity 中使用 ViewPager,BottomSheetFragment 和 DialogFragment 时,都应使用 getSupportFragmentManager;
-
在fragment 中使用 ViewPager 时应该使用 getChildFragmentManager
getSupportFragmentManager
与 activity 关联,可以将其视为 activity 的FragmentManager
;getChildFragmentManager
与fragment
关联,可以将其视为 fragment 的 FragmentManager;getParentFragmentManager
情况稍微复杂,正常情况返回的是该fragment
依附的 activity 的FragmentManager
。如果该 fragment 是另一个 fragment 的子 fragment,则返回的是其父 fragment 的getChildFragmentManager
。
错误的在 fragment 中使用 activity 的 FragmentManager 会引发内存泄露。 为什么呢?
假如您的 fragment 中有一些依靠 ViewPager 管理的子 fragment,并且所有这些 fragment 都在 activity 中,因为您使用的是 activity 的 FragmentManager 。 现在,如果关闭您的父 fragment,它将被关闭,但不会被销毁,因为所有子 fragment 都处于活动状态,并且它们仍在内存中,从而导致泄漏。 它不仅会泄漏父 fragment,还会泄漏所有子 fragment,因为它们都无法从堆内存中清除。
FragmentStateAdapter 和 FragmentPagerAdapter
FragmentPagerAdapter
将整个 fragment 存储在内存中,如果 ViewPager 中使用了大量 fragment,则可能导致内存开销增加。FragmentStateAdapter
仅存储片段的 savedInstanceState
,并在失去焦点时销毁所有 fragment
。
issue 1 刷新 ViewPager 不生效
ViewPager 中的 fragment 是通过 activity 或 fragment 的 FragmentManager 管理的,FragmentManager 包含了 ViewPage 的所有 fragment 的实例,因此,当ViewPager 没有刷新时,它只是 FragmentManager 仍保留的旧 fragment 实例。 您需要找出为什么 FragmentManger 持有 fragment 实例的原因!
issue 2 在 ViewPage 中访问当前 fragment
如果遇到这种情况,我们一般在 adapter 内部创建 fragment 的数组列表,或者尝试使用某些标签访问 fragment。 不过还有另一种选择。
FragmentStateAdapter
和FragmentPagerAdapter
都提供方法setPrimaryItem()
。 可以用来设置当前 fragment,如下所示:
var fragment: ChildFragment? = null
override fun setPrimaryItem(container: ViewGroup, position: Int, any: Any) {
if (getChildFragment() != any) {
fragment = any as ChildFragment
}
super.setPrimaryItem(container, position, any)
}
fun getChildFragment(): ChildFragment? = fragment
//use
mAapter.getChildFragment()
add 和 replace 如何选择?
replace
删除现有 fragment 并添加一个新 fragment。 这意味着当您按下返回按钮时,将创建被替换的 fragment,并调用其onCreateView()
。 而 add 保留现有 fragment,并添加一个新fragment,这意味着现有 fragment 将处于活动状态,并且它们不会处于paused
状态。 因此,按下返回按钮时,现有fragment(添加新fragment之前的fragment)不会调用 onCreateView。 就 fragment 的生命周期事件而言,在 replace 的情况下将调用onPause
,onResume
,onCreateView
和其他生命周期事件,在 add 的情况下则不会。
如果不需要重新访问当前 fragment 并且不再需要当前 fragment,请使用replace。 另外,如果您的应用有内存限制,请考虑使用 replace。
observe LiveData 时传入 this
还是 viewLifecycleOwner
?
androidx fragment 1.2.0 起,添加了新的 Lint 检查,以确保您在
onCreateView()
、onViewCreated()
或onActivityCreated()
观察LiveData
时使用getViewLifecycleOwner()
。
使用 simpleName 作为 fragment 的 tag 有何风险?
supportFragmentManager.commit {
replace(R.id.content, MyFragment.newInstance("Fragment"),
MyFragment::class.java.simpleName)
addToBackStack(null)
}
上面那样写不会出现什么问题,但是…
val fragment = supportFragmentManager.findFragmentByTag(tag)
这样获取到的 fragment 可能不是想要的结果。为什么呢?
// 两个 fragment,经过混淆,它们变成
com.mypackage.FragmentA → com.mypackage.c.a
com.mypackage.FragmentB → com.mypackage.c.a.a
如果是 simpleName 呢?
com.mypackage.FragmentA → a
com.mypackage.FragmentB → a
所以在设置 tag 时尽量用全名或者常量。
在 BottomBarNavigation 和 NavigationDrawer 中如何使用 Fragment 多次添加?
当我们使用 BottomBarNavigation 和 NavigationDrawer 时,通常会看到诸如fragment 重建或多次添加相同 fragment 之类的问题。
在这种情况下,您可以使用 show / hide 而不是 add 或 replace。
返回栈
如果您想在 fragment 的一系列跳转中按返回键返回上一个 fragment,应该在commit transaction 之前调用 addToBackStack 方法。
// 使用该扩展 androidx.fragment:fragment-ktx:1.2.0 以上
parentFragmentManager.commit {
addToBackStack(null) // 删除 fragment 的时候,仅执行 onDestroyView
add<SecondFragment>(R.id.content)
}
网友评论