在
Android Jetpack
组件中,fragment
作为视图控制器之一占有很重要的位置。但由于其bug众多,暗坑无数,以至于 Square 有这样一篇博客:Advocating Against Android Fragments。github上的 Fragmentation 有着 9.4k 的star。而现在,
androidx fragment
稳定版已来到 1.2.2,让我们总结一下fragment
有哪些常见问题以及有哪些使用fragment
的新姿势
Fragment 常见的问题
-
getSupportFragmentManager , getParentFragmentManager 和 getChildFragmentManager
-
FragmentStateAdapter 和 FragmentPagerAdapter
-
add 和 replace
-
observe LiveData时传入 this 还是 viewLifecycleOwner
-
使用 simpleName 作为 fragment 的 tag 有何风险?
-
在 BottomBarNavigation 和 drawer 中如何使用Fragment多次添加?
-
返回栈
getSupportFragmentManager , getParentFragmentManager和getChildFragmentManager
FragmentManager
是androidx.fragment.app
(已弃用的不考虑)下的抽象类,创建用于 添加,移除,替换fragment
的事务(transaction
)
首先要确认一件事,getSupportFragmentManager()
是 FragmentActivity
下的方法
getParentFragmentManager
和 getChildFragmentManager
是 androidx.fragment.app.Fragment
下的方法,
其中
androidx.fragment 1.2.0
后getFragmentManager
与requireFragmentManager
已弃用
明确了这件事,接下来的就很清晰了
getSupportFragmentManager
与activity
关联,可以将其视为activity
的FragmentManager
getChildFragmentManager
与fragment
关联,可以将其视为fragment
的FragmentManager
getParentFragmentManager
情况稍微复杂,正常情况返回的是该fragment
依附的activity
的FragmentManager
。如果该fragment是另一个fragment
的子fragment
,则返回的是其父fragment
的getChildFragmentManager
如果这么说还不明白的话,我们可以做一个实践。
创建一个 activity
,一个父fragment
,一个子fragment
// activity
class MyActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager.commit {
add<ParentFragment>(R.id.content)
}
Log.i("MyActivity", "supportFragmentManager $supportFragmentManager")
}
}
class ParentFragment : Fragment(R.layout.fragment_parent) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
childFragmentManager.commit {
add<ChildFragment>(R.id.content)
}
Log.i("ParentFragment", "parentFragmentManager $parentFragmentManager")
Log.i("ParentFragment", "childFragmentManager $childFragmentManager")
}
}
class ChildFragment : Fragment(R.layout.fragment_child) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.i("ChildFragment", "parentFragmentManager $parentFragmentManager")
Log.i("ChildFragment", "childFragmentManager $childFragmentManager")
}
}
//log
I/MyActivity: supportFragmentManager FragmentManager{825dcef in HostCallbacks{14a13fc}}}
I/ParentFragment: parentFragmentManager FragmentManager{825dcef in HostCallbacks{14a13fc}}}
I/ParentFragment: childFragmentManager FragmentManager{df5de83 in ParentFragment{7cdd800}}}
I/ChildFragment: parentFragmentManager FragmentManager{df5de83 in ParentFragment{7cdd800}}}
I/ChildFragment: childFragmentManager FragmentManager{aba9afb in ChildFragment{5cea718}}}
因此
-
在
activity
中使用ViewPager
,BottomSheetFragment
和DialogFragment
时,都应使用getSupportFragmentManager
-
在
fragment
中使用ViewPager
时应该使用getChildFragmentManager
错误的在 fragment
中使用 activity
的 FragmentManager
会引发内存泄露。 为什么呢?假如您的fragment中有一些依靠 ViewPager
管理的子 fragment
,并且所有这些 fragment
都在 activity
中,因为您使用的是activity
的FragmentManager
。 现在,如果关闭您的父fragment
,它将被关闭,但不会被销毁,因为所有子fragment
都处于活动状态,并且它们仍在内存中,从而导致泄漏。 它不仅会泄漏父fragment
,还会泄漏所有子fragment
,因为它们都无法从堆内存中清除。
FragmentStateAdapter 和 FragmentPagerAdapter
FragmentPagerAdapter
将整个 fragment
存储在内存中,如果ViewPager
中使用了大量 fragment
,则可能导致内存开销增加。 FragmentStatePagerAdapter
仅存储片段的savedInstanceState
,并在失去焦点时销毁所有 fragment
。
让我们看看常见的两个问题
1. 刷新ViewPager不生效
ViewPager
中的 fragment
是通过 activity
或 fragment
的 FragmentManager
管理的,FragmentManager
包含了viewpager
的所有fragment
的实例
因此,当ViewPager
没有刷新时,它只是FragmentManager
仍保留的旧 fragment
实例。 您需要找出为什么FragmentManger
持有fragment
实例的原因。
2. 在Viewpager中访问当前fragment
这也是我们遇到的一个非常普遍的问题。 如果遇到这种情况,我们一般在 adapter
内部创建 fragment
的数组列表,或者尝试使用某些标签访问fragment
。 不过还有另一种选择。 FragmentStateAdapter
和FragmentPagerAdapter
都提供方法setPrimaryItem
。 可以用来设置当前fragment
,如下所示:
var fragment: ChildFragment? = null
override fun setPrimaryItem(container: ViewGroup