Fragment 与 Jetpack:一场优雅的组件协作
说实话,Fragment 这个组件在 Android 开发里一直是个让人又爱又恨的存在。爱的是它灵活的 UI 模块化能力,恨的是它那些年给我们挖过的坑——生命周期错乱、状态丢失、通信繁琐……
不过自从 Jetpack 家族全面接手后,Fragment 的体验好了不止一个档次。今天我就带你系统梳理一下 Fragment 与 Jetpack 的四大核心协作点:FragmentManager 与 FragmentTransaction、FragmentResult 通信机制、FragmentContainerView 与动画、Fragment 与 ViewModel 的共享。
核心观点:Jetpack 让 Fragment 从「能用」变成了「好用」。你不需要再手写一堆模板代码来管理 Fragment 的生命周期和通信了。
FragmentManager:你的 Fragment 管家
FragmentManager 说白了就是 Fragment 的「大管家」。它负责 Fragment 的添加、移除、查找、以及状态保存恢复。在 Jetpack 时代,我们主要通过 supportFragmentManager 来获取它。
我个人习惯在 Activity 里这样初始化 FragmentManager:
class MainActivity : AppCompatActivity() {
private val fragmentManager: FragmentManager = supportFragmentManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 添加一个 Fragment
if (savedInstanceState == null) {
fragmentManager.commit {
add<HomeFragment>(R.id.fragment_container)
}
}
}
}
小技巧:使用 commit { } 扩展函数比直接写 beginTransaction() 更简洁。这是 Fragment KTX 提供的语法糖,我强烈推荐。
这里有个坑——FragmentManager 的状态保存时机。我曾经在 onSaveInstanceState 之后提交事务,结果 Fragment 的状态全丢了。记住:commit() 必须在 onSaveInstanceState 之前调用,否则用 commitAllowingStateLoss() 但你要清楚后果。
FragmentTransaction:事务的艺术
FragmentTransaction 是 FragmentManager 提交操作的「载体」。每次添加、移除、替换 Fragment 都需要通过它来完成。Jetpack 引入了 commit { } 扩展函数,让代码更 Kotlin 化。
来看一个典型的事务操作:
supportFragmentManager.commit {
// 添加 Fragment 到回退栈
addToBackStack("detail")
// 设置动画
setCustomAnimations(
R.anim.slide_in_right,
R.anim.slide_out_left,
R.anim.slide_in_left,
R.anim.slide_out_right
)
// 替换 Fragment
replace<DetailFragment>(R.id.fragment_container, args = bundleOf("id" to 123))
}
注意:如果你不调用 addToBackStack(),按返回键时 Fragment 会直接被移除。我见过不少新手因为这个 bug 被测试打回来。
事务的提交方式也有讲究:
commit()— 异步提交,推荐commitNow()— 同步提交,慎用commitAllowingStateLoss()— 允许状态丢失,特殊情况用
嗯,这里要特别说一下 commitNow()。它会在当前线程立即执行事务,但如果你在 Activity 的生命周期方法里乱用,很容易引发 IllegalStateException。我一般只在 onCreate 里用,其他地方一律用 commit()。
FragmentResult:告别接口回调地狱
以前 Fragment 之间通信,要么用接口回调,要么用 EventBus,要么用广播。说实话,每种方式都有痛点。Jetpack 推出的 FragmentResult 机制,算是给了我们一个官方标准方案。
它的核心思想很简单:通过 FragmentManager 作为通信桥梁。发送方设置结果,接收方监听结果。
来看一个实际例子——从列表 Fragment 跳转到详情 Fragment,然后详情 Fragment 返回数据:
// 发送方(DetailFragment)
class DetailFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 点击返回按钮时发送结果
btnBack.setOnClickListener {
setFragmentResult("request_key", bundleOf("result" to "操作成功"))
parentFragmentManager.popBackStack()
}
}
}
// 接收方(ListFragment)
class ListFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 注册结果监听
parentFragmentManager.setFragmentResultListener("request_key") { _, bundle ->
val result = bundle.getString("result")
// 处理返回结果
showToast(result)
}
}
}
关键点:FragmentResult 的生命周期是安全的。当 Fragment 处于 STARTED 状态时才会收到回调,避免了空指针和状态丢失问题。
你可能会问:为什么用 parentFragmentManager 而不是 childFragmentManager?
原因很简单——两个 Fragment 属于同一个父 FragmentManager(通常是 Activity 的 supportFragmentManager)。如果它们有嵌套关系,才需要用对应的 FragmentManager。
我在项目中遇到过一个问题:两个 Fragment 不在同一个层级,结果死活收不到回调。后来发现是 FragmentManager 用错了。记住这个原则:谁管理你,你就用谁的 FragmentManager。
FragmentContainerView:动画与过渡的新宠
FragmentContainerView 是 Jetpack 专门为 Fragment 设计的容器。它替代了传统的 FrameLayout,提供了更好的动画支持和生命周期管理。
为什么推荐用它?说白了,它解决了两个痛点:
- 动画更流畅 — 支持
setFragmentResultListener和setExitTransition等过渡动画 - 生命周期更安全 — 自动处理 Fragment 的保存和恢复
在布局文件中这样使用:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.HomeFragment" />
动画方面,Jetpack 提供了 FragmentTransaction.setCustomAnimations() 和 setTransition() 两种方式。我个人更推荐前者,因为控制粒度更细。
来看一个带动画的 Fragment 切换:
supportFragmentManager.commit {
setCustomAnimations(
R.anim.fade_in,
R.anim.fade_out,
R.anim.fade_in,
R.anim.fade_out
)
replace<DetailFragment>(R.id.fragment_container)
addToBackStack(null)
}
避坑指南:我曾经在 FragmentContainerView 里嵌套 Fragment 时,发现动画不生效。后来排查发现是 setCustomAnimations 必须在 replace 之前调用。顺序很重要!
Fragment 与 ViewModel 的共享:数据驱动的正确姿势
Fragment 之间共享数据,最优雅的方式就是通过 ViewModel。Jetpack 的 ViewModel 天然支持 Activity 作用域,这意味着同一个 Activity 下的所有 Fragment 可以共享同一个 ViewModel 实例。
实现方式很简单:
// 共享的 ViewModel
class SharedViewModel : ViewModel() {
private val _selectedItem = MutableLiveData<Item>()
val selectedItem: LiveData<Item> = _selectedItem
fun selectItem(item: Item) {
_selectedItem.value = item
}
}
// Fragment A
class ListFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnSelect.setOnClickListener {
sharedViewModel.selectItem(item)
}
}
}
// Fragment B
class DetailFragment : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel.selectedItem.observe(viewLifecycleOwner) { item ->
// 更新 UI
showDetail(item)
}
}
}
核心要点:使用 by activityViewModels() 而不是 by viewModels()。前者获取的是 Activity 作用域的 ViewModel,后者是 Fragment 作用域的。搞混了数据就不共享了。
你想想看,这种模式的好处是什么?
- 解耦 — Fragment 之间不需要互相持有引用
- 生命周期安全 — ViewModel 自动处理配置变更
- 数据驱动 — 用 LiveData 或 Flow 观察数据变化
不过要注意一点:ViewModel 不要持有 Fragment 的引用。我见过有人把 Context 传进 ViewModel,结果导致内存泄漏。ViewModel 的生命周期比 Fragment 长,你想想看,如果 Fragment 销毁了但 ViewModel 还活着,它持有的引用就成了野指针。
好了,以上就是 Fragment 与 Jetpack 四大核心协作点的全面解析。从 FragmentManager 的事务管理,到 FragmentResult 的通信机制,再到 FragmentContainerView 的动画支持,最后是 ViewModel 的数据共享——每一个环节都是 Jetpack 对 Fragment 生态的深度优化。
我个人觉得,Fragment 在 Jetpack 的加持下,已经不再是那个让人头疼的组件了。只要遵循这些最佳实践,你完全可以写出优雅、稳定、可维护的 Fragment 代码。