第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. 步骤1:定义 ViewModel 类 —— 创建普通的 ViewModel 子类,无需特殊标记。
  2. 步骤2:在 Activity 中初始化(可选) —— 若需要 Activity 自身也观察数据,可在 Activity 中获取一次。
  3. 步骤3:在 Fragment 中选择获取方式
    • 用 activityViewModels() 获得 Activity 级共享 ViewModel。
    • 用 viewModels() 获得 Fragment 级独立 ViewModel。
  4. 步骤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 作用域关系结构图

ViewModel 作用域对比:Activity 级共享 vs Fragment 级独立 Activity 级共享作用域 Activity(ViewModelStore) SharedViewModel(唯一实例) FragmentA FragmentB activityViewModels() FragmentA 更新数据 → SharedViewModel → FragmentB 自动收到通知 Fragment 级独立作用域 FragmentC ViewModelC(独立) FragmentD ViewModelD(独立) viewModels() viewModels() FragmentC 更新 → 仅 ViewModelC 变化 FragmentD 完全隔离,互不影响 关键区别:共享作用域 = 同一个 ViewModel 实例;独立作用域 = 各自 ViewModel 实例

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 实例 作为共享作用域的宿主。