Fragment 与 Jetpack:一场优雅的组件协作

说实话,Fragment 这个组件在 Android 开发里一直是个让人又爱又恨的存在。爱的是它灵活的 UI 模块化能力,恨的是它那些年给我们挖过的坑——生命周期错乱、状态丢失、通信繁琐……

不过自从 Jetpack 家族全面接手后,Fragment 的体验好了不止一个档次。今天我就带你系统梳理一下 Fragment 与 Jetpack 的四大核心协作点:FragmentManager 与 FragmentTransaction、FragmentResult 通信机制、FragmentContainerView 与动画、Fragment 与 ViewModel 的共享。

核心观点:Jetpack 让 Fragment 从「能用」变成了「好用」。你不需要再手写一堆模板代码来管理 Fragment 的生命周期和通信了。

Fragment + Jetpack FragmentManager 管理 Fragment 生命周期 FragmentTransaction 添加/移除/替换 Fragment FragmentResult 跨 Fragment 通信 FragmentContainerView 动画与过渡 ViewModel 共享 Activity 作用域 Jetpack 统一管理,告别碎片化

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,提供了更好的动画支持和生命周期管理。

为什么推荐用它?说白了,它解决了两个痛点:

  1. 动画更流畅 — 支持 setFragmentResultListenersetExitTransition 等过渡动画
  2. 生命周期更安全 — 自动处理 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 代码。


公众号:蓝海资料掘金营,微信deep3321