第31章:Fragment生命周期、FragmentManager、Fragment通信、ViewPager2
Fragment,说实话,是很多Kotlin工程师又爱又恨的东西。爱它灵活,恨它复杂。我最早接触Fragment时,总觉得它就是个「小Activity」,后来踩了不少坑才明白——它远比Activity要精妙,也远比Activity要坑多。
这一章,咱们就把Fragment的四个核心话题聊透:生命周期、FragmentManager、通信机制、以及ViewPager2的配合。嗯,都是实战中绕不开的硬骨头。
31.1 Fragment的生命周期——你真的懂了吗?
Fragment的生命周期方法比Activity多,而且多了几个关键回调。我习惯把它们分成三组来记:
| 分组 | 回调方法 | 说明 |
|---|---|---|
| 创建阶段 | onAttach → onCreate → onCreateView → onViewCreated → onActivityCreated | Fragment与Activity绑定、创建视图 |
| 运行阶段 | onStart → onResume | Fragment可见可交互 |
| 销毁阶段 | onPause → onStop → onDestroyView → onDestroy → onDetach | 视图销毁、Fragment解绑 |
这里有个关键点:onCreateView和onViewCreated的区别。onCreateView只负责inflate布局,而onViewCreated才是你真正操作视图的地方。我见过不少新手在onCreateView里直接findViewById,结果空指针——因为视图还没完全准备好。
核心原则:视图操作放在onViewCreated里,数据初始化放在onCreate里,资源释放放在onDestroyView里。
还有一个坑:Fragment的生命周期与Activity的生命周期是联动的,但不是完全同步的。比如Activity执行onCreate时,Fragment可能才执行到onAttach或onCreate。你想想看,如果你在Activity的onCreate里直接操作Fragment的视图,大概率会崩。
我曾经在项目里遇到一个bug:Activity旋转屏幕后,Fragment的onCreateView被调用了两次,导致视图重叠。后来发现是因为没有在onSaveInstanceState里保存状态,Fragment被重建了两次。嗯,这个坑我记了很久。
31.2 FragmentManager——管理Fragment的指挥官
FragmentManager,说白了就是用来管理Fragment的增删改查。每个Activity都有一个FragmentManager,通过supportFragmentManager获取。
我个人习惯把FragmentManager的操作分为两类:
- 事务操作:add、replace、remove、hide、show
- 查询操作:findFragmentById、findFragmentByTag、getFragments
来看一个典型的事务操作:
supportFragmentManager.commit {
replace(R.id.fragment_container, MyFragment())
addToBackStack("my_fragment")
}
这里有个细节:addToBackStack会把当前事务加入返回栈。不加的话,按返回键就直接退出Activity了。我刚开始做项目时经常忘记加,用户体验很差。
小技巧:如果你只是临时显示一个Fragment,用add而不是replace。add不会销毁之前的Fragment,切换更流畅。我一般在底部导航栏的场景用add + hide/show组合。
还有一个容易被忽略的点:FragmentManager的状态保存。当Activity被系统回收时,FragmentManager会自动保存Fragment的状态。但如果你在Fragment里持有了一些非序列化的对象(比如Bitmap),那恢复时就会出问题。我的做法是:在onSaveInstanceState里只保存轻量级数据,复杂对象通过ViewModel来持有。
31.3 Fragment通信——别再用接口回调了
Fragment之间的通信,传统做法是定义接口,然后在Activity里做桥接。说实话,这种方式太啰嗦了。现在有了ViewModel + LiveData/Flow,通信变得简单多了。
我推荐的做法是:共享ViewModel。两个Fragment共用同一个ViewModel实例,数据变化自动通知。
// 在Fragment A中
class FragmentA : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel.selectedData.observe(viewLifecycleOwner) { data ->
// 更新UI
}
}
}
// 在Fragment B中
class FragmentB : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
private fun sendData(data: String) {
sharedViewModel.updateData(data)
}
}
为什么用activityViewModels()?因为它返回的是Activity作用域的ViewModel,两个Fragment拿到的其实是同一个实例。这样数据就共享了。
注意:如果Fragment之间需要传递一次性事件(比如点击按钮后跳转),用LiveData可能会收到重复事件。我建议用SingleLiveEvent或者Channel来处理。
还有一种场景:Fragment向Activity传递数据。直接用ViewModel就行,Activity也可以通过viewModels()拿到同一个实例。不需要再写接口了。
31.4 ViewPager2 + Fragment——最佳搭档
ViewPager2是ViewPager的升级版,它解决了ViewPager的很多痛点。比如:支持垂直滑动、支持RTL布局、基于RecyclerView实现所以性能更好。
ViewPager2和Fragment配合使用时,核心是FragmentStateAdapter:
class MyPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
private val fragments = listOf(
FragmentA(),
FragmentB(),
FragmentC()
)
override fun getItemCount(): Int = fragments.size
override fun createFragment(position: Int): Fragment = fragments[position]
}
然后在Activity里设置:
viewPager2.adapter = MyPagerAdapter(this)
viewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL
这里有个坑:FragmentStateAdapter默认会预加载相邻的Fragment。如果你有3个页面,滑动到第1页时,第2页就已经被创建了。这会导致不必要的网络请求或资源消耗。
我曾经在一个图片浏览器的项目里,ViewPager2预加载导致内存飙升。后来通过设置viewPager2.offscreenPageLimit = 0来关闭预加载,但要注意:这个API在ViewPager2里已经被标记为deprecated了。替代方案是使用setOffscreenPageLimit并传一个较小的值。
还有一个常见问题:ViewPager2嵌套Fragment时的生命周期管理。当Fragment被滑出屏幕时,它不会立即销毁,而是进入onPause或onStop状态。如果你在Fragment里做了定时任务或动画,记得在onPause里暂停,在onResume里恢复。
建议:使用viewLifecycleOwner来管理协程和LiveData的观察。这样当Fragment的视图被销毁时,协程会自动取消,不会造成内存泄漏。
31.5 知识体系总览
下面这张图是我整理的Fragment核心知识结构,你可以对照着回顾一下:
这张图把Fragment的核心知识点分成了五个模块:生命周期、FragmentManager、通信、ViewPager2、以及最佳实践。每个模块之间都有联系,但你可以单独攻克。
好了,这一章的内容就到这里。Fragment的知识点其实很多,但只要你掌握了生命周期、事务管理、通信方式这三个核心,再配合ViewPager2的使用,大部分场景都能应对。嗯,剩下的就是多写多练了。
公众号:蓝海资料掘金营,微信deep3321