一句话总结:
Fragment 和 Activity 的通信就像两个人打电话:Fragment 拨号(定义接口),Activity 接电话(实现接口);而更现代的方式是,Activity 直接喊话(调用 Fragment 方法),或者通过“共享的智能中间人”(ViewModel)来传递和同步消息。
一、范式革命:为什么共享 ViewModel 是现代架构的首选?
在现代 Android 开发中,共享的 ViewModel 不仅仅是一种通信方式,它是官方推荐的、用于解耦 UI 控制器并管理 UI 状态的核心组件。通信是其天然的副产品。
-
核心思想:Activity 和其内部的多个 Fragment 共享同一个
ViewModel实例。它们之间不直接对话,而是通过修改和观察ViewModel中的数据(通常是LiveData或StateFlow)来间接通信。 -
优势:
- 完全解耦:Fragment 和 Activity 都不需要持有对方的引用。
- 生命周期安全:
ViewModel能在配置变更(如屏幕旋转)中存活,数据不会丢失。 - 数据唯一源:状态数据集中存储在
ViewModel中,避免了数据不一致的问题。
实现步骤:
-
创建共享的 ViewModel:
class SharedViewModel : ViewModel() { private val _selectedItem = MutableLiveData<Item>() val selectedItem: LiveData<Item> = _selectedItem fun selectItem(item: Item) { _selectedItem.value = item } } -
在 Activity 和 Fragment 中获取 ViewModel 实例:
关键在于,它们都使用 Activity 作为 ViewModelStoreOwner 来获取 ViewModelProvider,从而确保获取到的是同一个实例。
// 在 Activity 中 val viewModel: SharedViewModel by viewModels() // 在 Fragment 中 val viewModel: SharedViewModel by activityViewModels() // 使用 KTX 扩展,等同于 ViewModelProvider(requireActivity()).get(...) -
通信:
// FragmentA (发送方) button.setOnClickListener { viewModel.selectItem(someItem) } // Activity 或 FragmentB (接收方) viewModel.selectedItem.observe(viewLifecycleOwner) { item -> // 当数据变化时,更新UI updateUiWith(item) }
二、事件与结果传递:Fragment Result API
对于一次性的事件或结果传递(例如,一个对话框 Fragment 将用户的“确定/取消”选择结果返回),使用 ViewModel 的 LiveData 会有些笨重。为此,Jetpack 提供了更轻量级的 Fragment Result API。
- 核心思想:它就像一个组件间的“事件总线”,通过一个
requestKey来发送和接收带Bundle数据的事件。
实现步骤:
-
接收方 (Activity 或 Fragment) 注册监听器:
// 在 Activity 的 onCreate 或 Fragment 的 onViewCreated 中 supportFragmentManager.setFragmentResultListener("requestKey", this) { requestKey, bundle -> // 当有结果返回时,此回调被触发 val result = bundle.getString("bundleKey") // 处理结果 } -
发送方 (Fragment) 发送结果:
button.setOnClickListener { val resultBundle = Bundle().apply { putString("bundleKey", "This is a result") } // 使用相同的 requestKey 发送结果 setFragmentResult("requestKey", resultBundle) // (可选) 关闭当前 Fragment parentFragmentManager.popBackStack() }
结论:Fragment Result API 是对传统接口回调模式的现代化、官方级替代方案。
三、传统但仍有效的通信模式
1. 初始化数据传递:Bundle 与 newInstance 工厂方法
这是唯一推荐的向 Fragment 传递初始数据的方式。因为它能保证在 Fragment 被系统销毁并重建时,参数能够被正确恢复。
class DetailFragment : Fragment() {
companion object {
fun newInstance(itemId: String): DetailFragment {
return DetailFragment().apply {
arguments = Bundle().apply {
putString("ITEM_ID", itemId)
}
}
}
}
}
// Activity 中调用: supportFragmentManager.commit { replace(R.id.container, DetailFragment.newInstance("123")) }
2. 遗留方案:接口回调
虽然样板代码多,但在一些简单或遗留项目中仍然可见。它的主要问题是导致 Fragment 与其宿主(通常是 Activity)的强耦合。
四、选型指南:在何时使用何种通信方式?
| 通信场景 | 首选方案 | 备选/特定场景方案 |
|---|---|---|
| 共享UI状态、数据流 (如用户登录状态、列表数据) | 共享 ViewModel | - |
| 一次性事件/结果 (如Dialog选择、表单提交) | Fragment Result API | 接口回调 (不推荐) |
| 向 Fragment 传递初始配置数据 | Bundle + newInstance() | - |
| Fragment 间的兄弟通信 | 共享 ViewModel | Fragment Result API |
对 Fragment 的直接命令式控制 (如 fragment.reset()) | 直接调用方法 (慎用) | 尽量通过 ViewModel 暴露事件来驱动 |