4. 在兄弟 Fragment 之间共享 ViewModel:requireActivity().let { ViewModelProvider(it).get(SharedViewModel::class.java) }
兄弟 Fragment 是指附加到同一 Activity 但彼此没有直接嵌套关系的 Fragment。当两个兄弟 Fragment 需要共享状态(如用户选择、表单数据或实时列表)时,传统做法是通过 Activity 中介回调,接口繁琐且易出错。Android 架构组件提供了更简洁的方案:将 ViewModel 的作用域限定在 Activity 生命周期,使所有附加到该 Activity 的 Fragment 能访问同一个 ViewModel 实例。
4.1 原理:Activity 作用域的 ViewModel
ViewModelProvider 的构造参数决定了 ViewModel 的存储范围。当传入 activity 作为 ViewModelStoreOwner 时,ViewModel 会保存在 Activity 的 ViewModelStore 中,Activity 生命周期结束后才被清除。所有通过 ViewModelProvider(activity).get(SharedViewModel::class.java) 获取的 Fragment 都会得到完全相同的实例,从而实现兄弟 Fragment 间的数据共享。
核心 API 对比:
ViewModelProvider(fragment).get(Xxx::class.java)→ 作用域为 Fragment 自身,每个 Fragment 独立实例。ViewModelProvider(activity).get(Xxx::class.java)→ 作用域为 Activity,所有 Fragment 共享同一实例。
4.2 实现流程
- 创建共享 ViewModel 类:继承 ViewModel,包含需要共享的数据(推荐使用 LiveData 或 StateFlow)。
- 在 Fragment A 中获取实例:调用
requireActivity().let { ViewModelProvider(it).get(SharedViewModel::class.java) },并读取/写入数据。 - 在 Fragment B 中获取实例:使用完全相同的代码获取同一个 ViewModel 实例,并观察数据变化。
- 确保 Activity 不提前销毁:Fragment 附加的 Activity 必须在整个交互过程中存活(典型场景:单 Activity 应用)。
- 清理工作:当不再需要共享时(例如 Fragment 被移除),ViewModel 会自动保留直到 Activity 销毁,无需手动清理。
4.3 代码示例
SharedViewModel.kt
class SharedViewModel : ViewModel() {
private val _selectedItem = MutableLiveData()
val selectedItem: LiveData get() = _selectedItem
fun selectItem(item: String) {
_selectedItem.value = item
}
}
FragmentA.kt(发送方)
class FragmentA : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = requireActivity().let {
ViewModelProvider(it).get(SharedViewModel::class.java)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnSubmit.setOnClickListener {
viewModel.selectItem("选项A")
}
}
}
FragmentB.kt(接收方)
class FragmentB : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = requireActivity().let {
ViewModelProvider(it).get(SharedViewModel::class.java)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.selectedItem.observe(viewLifecycleOwner) { item ->
// 更新 UI
textSelected.text = "已选择: $item"
}
}
}
4.4 API 详解
| API | 说明 |
|---|---|
requireActivity() | 返回当前 Fragment 附加的 Activity 实例,若 Fragment 未附加则抛出异常。 |
ViewModelProvider(storeOwner) | 创建 ViewModelProvider,storeOwner 可以是 Activity、Fragment 或 NavigationBackStackEntry。 |
.get(ViewModelClass) | 返回与 storeOwner 关联的 ViewModel 实例。若已存在则复用,否则通过默认构造函数创建。 |
.let { ... } | Kotlin 作用域函数,将对象作为 lambda 接收者,避免重复调用 requireActivity()。 |
4.5 调试验证
- 在两个 Fragment 中分别设置断点,观察
viewModel的 hashCode 是否相同。 - 使用
adb shell dumpsys activity activities检查 Activity 的 ViewModelStore 状态。 - 在 SharedViewModel 中添加
init { Log.d("VM", "Created: $this") },确认只打印一次。
4.6 常见错误
错误 1:在 Fragment 中使用
会导致每个 Fragment 获得独立的 ViewModel 实例,兄弟 Fragment 无法共享数据。
ViewModelProvider(this)会导致每个 Fragment 获得独立的 ViewModel 实例,兄弟 Fragment 无法共享数据。
错误 2:在 Fragment 的
ViewModel 会在 Activity 重建后继续存在,但 Fragment 重新创建时必须重新绑定观察者。确保在
onCreateView 或 onViewCreated 中获取 ViewModel 后,未考虑 Fragment 生命周期重建ViewModel 会在 Activity 重建后继续存在,但 Fragment 重新创建时必须重新绑定观察者。确保在
onCreate 中获取实例,避免重复创建。
错误 3:在 Activity 中使用
如果在 Fragment 尚未附加到 Activity 时调用(如构造函数中),会抛出
requireActivity() 导致空指针如果在 Fragment 尚未附加到 Activity 时调用(如构造函数中),会抛出
IllegalStateException。应将获取逻辑放在 onAttach 之后。
4.7 性能与稳定性优化
- 避免在 ViewModel 中持有 Activity/Context 引用:防止内存泄漏。推荐使用
LiveData或StateFlow传递数据。 - 使用
viewLifecycleOwner而非this:在 Fragment 中观察 LiveData 时,使用viewLifecycleOwner可确保在视图销毁时自动解绑。 - 限制共享数据粒度:不要将整个 Activity 或大型 Bitmap 放入 ViewModel,使用轻量级值对象。
- 考虑 Navigation 组件场景:若使用 Navigation,可考虑将 ViewModel 作用域限定到 NavBackStackEntry,避免所有 Fragment 共享。
4.8 共享流程 SVG 图
4.9 完整使用模板(推荐写法)
// 在基类或工具类中封装,避免重复代码
inline fun <reified VM : ViewModel> Fragment.sharedViewModel(): VM {
return requireActivity().let {
ViewModelProvider(it).get(VM::class.java)
}
}
// 在 Fragment A 中使用
val viewModel: SharedViewModel by lazy { sharedViewModel() }
// 在 Fragment B 中使用(代码完全一致)
val viewModel: SharedViewModel by lazy { sharedViewModel() }
最佳实践:将共享 ViewModel 的获取封装为扩展函数,减少代码重复,并统一管理作用域。若项目使用 Koin 或 Hilt,可进一步将 ViewModel 注入与 Activity 作用域绑定。
通过以上方式,兄弟 Fragment 之间无需接口或 EventBus,即可简洁、可靠地共享状态。此模式尤其适合仪表板、表单分页、选项卡等场景。