第3章 作用域本质:Activity级共享与Fragment级独立的区别
ViewModel 的作用域由 ViewModelStoreOwner 接口决定。Activity 和 Fragment 各自实现了该接口,因此 ViewModel 的生命周期与它们绑定。理解 Activity 级共享和 Fragment 级独立两种作用域,是正确设计 Fragment 间通信、避免数据错乱或内存泄漏的前提。
3.1 作用域绑定原理
每个 ViewModelStoreOwner 持有一个 ViewModelStore 容器。当通过 ViewModelProvider 获取 ViewModel 时,框架会以 类名 + 作用域 为键,在 ViewModelStore 中查找或创建实例。
- Activity 作用域:Activity 的 ViewModelStore 存活于 Activity 整个生命周期(从 onCreate 到 onDestroy)。
- Fragment 作用域:Fragment 的 ViewModelStore 存活于 Fragment 的生命周期(从 onAttach 到 onDestroy)。
- 共享机制:多个 Fragment 如果传入同一个 Activity 的 ViewModelStore,则获取到相同的 ViewModel 实例。
关键结论: ViewModel 的实例数 = 作用域个数。Activity 级 ViewModel 只有 1 个 实例;每个 Fragment 独立 ViewModel 各有 1 个 实例。
3.2 作用域对比表
| 对比维度 | Activity 级共享 | Fragment 级独立 |
|---|---|---|
| 作用域宿主 | Activity 的 ViewModelStore | Fragment 自身的 ViewModelStore |
| 实例数量 | 唯一 1 个(所有子 Fragment 共享) | 每个 Fragment 各自 1 个 |
| 获取方式 | ViewModelProvider(activity).get(...) 或 activityViewModels() |
ViewModelProvider(this).get(...) 或 viewModels() |
| 数据共享范围 | Activity 内所有 Fragment + Activity 自身 | 仅限当前 Fragment 内部 |
| 生命周期跟随 | Activity 的 onCreate → onDestroy | Fragment 的 onAttach → onDestroy |
| 典型场景 | 多 Fragment 共享表单数据、Tab 同步状态 | Fragment 内部 UI 状态、列表筛选条件 |
3.3 实现流程:两种作用域的构建
- 步骤1:定义 ViewModel 类 —— 创建普通的 ViewModel 子类,无需特殊标记。
- 步骤2:在 Activity 中初始化(可选) —— 若需要 Activity 自身也观察数据,可在 Activity 中获取一次。
- 步骤3:在 Fragment 中选择获取方式
- 用
activityViewModels()获得 Activity 级共享 ViewModel。 - 用
viewModels()获得 Fragment 级独立 ViewModel。
- 用
- 步骤4:通过 LiveData/Flow 观察数据变化 —— 共享作用域下,一个 Fragment 更新数据,另一个 Fragment 立即收到通知。
// SharedViewModel.kt — 普通的 ViewModel 类
class SharedViewModel : ViewModel() {
private val _sharedText = MutableLiveData<String>("")
val sharedText: LiveData<String> get() = _sharedText
fun updateText(text: String) {
_sharedText.value = text
}
}
// MainActivity.kt — 宿主 Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 可选:Activity 自身也获取 ViewModel
val vm = ViewModelProvider(this)[SharedViewModel::class.java]
vm.sharedText.observe(this) { text ->
Log.d("Activity", "共享数据: $text")
}
}
}
// FragmentA.kt — 使用 Activity 级共享作用域
class FragmentA : Fragment() {
private val sharedVM: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedVM.updateText("来自 FragmentA 的更新")
sharedVM.sharedText.observe(viewLifecycleOwner) { text ->
// 当 FragmentB 更新时也会收到通知
Log.d("FragmentA", "当前文本: $text")
}
}
}
// FragmentB.kt — 同样使用 Activity 级共享作用域
class FragmentB : Fragment() {
private val sharedVM: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedVM.updateText("来自 FragmentB 的更新")
sharedVM.sharedText.observe(viewLifecycleOwner) { text ->
Log.d("FragmentB", "当前文本: $text")
}
}
}
// FragmentC.kt — 使用 Fragment 级独立作用域
class FragmentC : Fragment() {
private val independentVM: SharedViewModel by viewModels() // 独立实例
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
independentVM.updateText("FragmentC 的独立数据")
independentVM.sharedText.observe(viewLifecycleOwner) { text ->
// 此 ViewModel 仅 FragmentC 可见,与其他 Fragment 隔离
Log.d("FragmentC", "独立数据: $text")
}
}
}
3.4 作用域关系结构图
3.5 调试技巧:验证作用域归属
在运行时确认 ViewModel 是否为同一实例,最直接的方式是检查 hashCode 或 toString() 输出。
// 在 FragmentA 和 FragmentB 中分别打印
Log.d("ScopeTest", "FragmentA ViewModel: ${sharedVM.hashCode()}")
Log.d("ScopeTest", "FragmentB ViewModel: ${sharedVM.hashCode()}")
// 预期输出(共享作用域):
// FragmentA ViewModel: 123456
// FragmentB ViewModel: 123456 ← 相同 hash,证明是同一实例
// 在 FragmentC 中打印独立 ViewModel:
Log.d("ScopeTest", "FragmentC ViewModel: ${independentVM.hashCode()}")
// 输出:FragmentC ViewModel: 789012 ← 与上述 hash 不同,独立实例
调试建议: 使用 Android Studio 的 Layout Inspector 或 Live Data 探针 观察 ViewModel 内部状态变化。当 FragmentA 更新数据时,若 FragmentB 的观察者同步触发,则确认作用域共享生效。
3.6 常见错误与避坑
| 错误场景 | 原因 | 解决方案 |
|---|---|---|
| Fragment 之间数据不同步 | 各自用了 viewModels() 而非 activityViewModels() |
将获取方式改为 activityViewModels() |
| Fragment 销毁后 ViewModel 仍然持有旧数据 | Fragment 独立 ViewModel 的生命周期与 Fragment 绑定,Fragment 重建时会创建新实例 | 使用 SavedStateHandle 保存关键状态,或切换为 Activity 级共享作用域 |
在 Fragment 中通过 requireActivity() 获取 ViewModel 导致泄漏 |
错误的 ViewModelStoreOwner 传参,造成作用域错乱 | 始终使用 activityViewModels() 委托或显式传入 requireActivity() |
| 多个 Activity 复用同一个 ViewModel 类 | 误以为类名相同就是同一个实例 | 不同 Activity 的 ViewModelStore 隔离,必然不是同一实例;需要跨 Activity 共享请使用 SavedStateHandle + Bundle 或持久化方案 |
3.7 性能与稳定性优化
- 避免大对象共享:Activity 级 ViewModel 存活时间长,不要在共享 ViewModel 中存放大量 Bitmap 或集合,防止内存泄漏。
- 按需选择作用域:如果数据只需要在单个 Fragment 内部使用,优先用
viewModels()独立作用域,减少耦合。 - 清理订阅:使用
viewLifecycleOwner观察 LiveData/Flow,确保 Fragment 视图销毁时自动解注册。 - 测试作用域隔离:在单元测试中为每个 ViewModel 创建独立的
ViewModelStore,避免测试间数据污染。
⚠️ 重要提醒: 永远不要将 Fragment 的
this 作为 ViewModelStoreOwner 传给 ViewModelProvider 来模拟“共享”——这样每个 Fragment 都会获得自己的独立实例,完全丧失共享能力。务必使用 Activity 实例 作为共享作用域的宿主。