第3章 跨模块ViewModel共享方案
3.1 为什么需要跨模块共享 ViewModel
在模块化架构中,不同模块的 Fragment 或 Activity 经常需要共享同一份界面状态或业务数据。例如:商品详情模块与购物车模块都需要监听同一购物车计数;用户模块与订单模块都需要读取登录态。若每个模块各自创建 ViewModel,数据将无法同步,也无法实现跨模块通信。
Android 的 ViewModel 本身支持按作用域(Scope)共享——同一个 ViewModelStoreOwner 内通过相同 key 创建的 ViewModel 实例唯一。因此关键抉择在于:选择 Activity 作为作用域(ActivityScope)还是选择父 Fragment 作为作用域(FragmentScope)。本章节将解析两种作用域的差异、实现流程及模块化落地细节。
3.2 作用域原理
ViewModel 实例由 ViewModelProvider 管理,其内部通过 ViewModelStore 缓存实例。生命周期由 ViewModelStoreOwner(Activity 或 Fragment)决定:
- ActivityScope:ViewModel 与 Activity 生命周期绑定,Activity 销毁时(非 configuration change)ViewModel 才销毁。所有挂载在该 Activity 下的 Fragment 均可共享同一个 ViewModel 实例。
- FragmentScope:ViewModel 与父 Fragment 生命周期绑定,父 Fragment 销毁时 ViewModel 销毁。仅在该父 Fragment 及其子 Fragment 间共享。
核心 API 为 ViewModelProvider(storeOwner),其中 storeOwner 决定了作用域。
| 维度 | ActivityScope | FragmentScope |
|---|---|---|
| Owner 类型 | Activity | Fragment |
| 生命周期 | 与 Activity 一致 | 与父 Fragment 一致 |
| 共享范围 | Activity 内所有 Fragment | 父 Fragment 及其子 Fragment |
| 内存释放 | Activity 销毁时 | 父 Fragment 销毁时 |
| 模块化耦合 | 各模块需依赖公共 Activity | 各模块需依赖公共父 Fragment |
| 适用场景 | 跨多个 Fragment 页面的全局状态(如购物车、登录态) | 仅在一个复合 Fragment 内部的子页面间共享数据(如多步骤表单) |
最佳实践:对于跨模块共享,优先使用 ActivityScope 以保证足够的作用域范围;若业务明确限定在单个 Feature 内部,使用 FragmentScope 可减少内存驻留。
3.3 实现流程
- 定义 SharedViewModel:将 ViewModel 类放在公共模块(如
common或shared模块),使其可被多个模块依赖。 - 决定作用域类型:
- ActivityScope:在
Activity中创建 ViewModel,Fragment 通过requireActivity().viewModels<SharedVM>()获取。 - FragmentScope:在父 Fragment 中创建 ViewModel,子 Fragment 通过
requireParentFragment().viewModels<SharedVM>()获取。
- ActivityScope:在
- 使用 Hilt 注入(推荐):通过
@HiltViewModel和hiltViewModel()扩展函数自动关联作用域。 - 数据通信:使用
LiveData或StateFlow暴露状态,各 Fragment 仅做观察。
// 公共模块中的 SharedViewModel.kt
class CartViewModel @Inject constructor(
private val cartRepository: CartRepository
) : ViewModel() {
private val _count = MutableStateFlow(0)
val count: StateFlow<Int> = _count.asStateFlow()
fun addToCart(productId: String) {
viewModelScope.launch {
cartRepository.addItem(productId)
_count.value = cartRepository.getItemCount()
}
}
}
// ActivityScope 获取(在 DetailFragment 中)
class DetailFragment : Fragment() {
private val cartViewModel: CartViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 直接使用 cartViewModel
viewLifecycleOwner.lifecycleScope.launch {
cartViewModel.count.collect { count ->
// 更新 UI
}
}
}
}
// FragmentScope 获取(在 ChildFragment 中)
class ChildFragment : Fragment() {
private val parentViewModel: CartViewModel? by parentViewModels() // 父 Fragment 需先创建
}
// Hilt 简化版(Activity/Fragment 内)
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val cartViewModel: CartViewModel by viewModels() // 默认 ActivityScope
}
@AndroidEntryPoint
class DetailFragment : Fragment() {
private val cartViewModel: CartViewModel by hiltViewModel() // 也默认 ActivityScope
}
3.4 作用域选择流程图
下图展示了 ActivityScope 与 FragmentScope 在模块化布局中的共享区别。
3.5 常见错误与调试
| 错误 | 现象 | 原因 | 解决方案 |
|---|---|---|---|
| 作用域不匹配 | Fragment 获取 ViewModel 时返回新实例,状态不共享 | 使用了 by viewModels()(默认 Fragment 作用域)而不是 by activityViewModels() | 检查 ViewModelProvider 传入的 owner 是否为目标 Owner |
| 内存泄漏 | Activity 销毁后 ViewModel 仍然存在 | ViewModel 内部持有 Activity 引用或长期后台协程 | 严格使用 viewModelScope,避免引用 Activity |
| Hilt 注入失败 | 构建时 @HiltViewModel 报错 | 未在 Fragment 添加 @AndroidEntryPoint 或未引入 Hilt 依赖 | 确保所有参与的 Fragment 均添加注解,并在模块 build.gradle 添加 Hilt 插件 |
| 跨模块依赖混乱 | 编译时找不到 ViewModel 类 | SharedViewModel 模块未被依赖 | 在需要使用的模块 build.gradle 中添加 implementation project(':shared') |
调试技巧:在 Fragment 的
onCreate 或 onViewCreated 中打印 Log.d("ViewModelScope", "VM hash: ${viewModel.hashCode()}"),对比多个 Fragment 是否输出相同 hashCode。若不同,则作用域未对齐。3.6 性能与稳定性优化
- 减少共享数据体积:只在 SharedViewModel 中存放跨模块必要的聚合状态,避免存放列表大数据(如商品列表),可通过 Repository 按需加载。
- 使用 StateFlow 替代 LiveData:配合
SharingStarted.WhileSubscribed(5000)避免后台浪费。 - 清理回调:SharedViewModel 中若注册了系统广播或监听器,务必在
onCleared()中注销。 - 避免跨模块直接暴露 ViewModel 对象:建议通过接口层(如
CartStateProvider)定义协议,模块只依赖接口。
3.7 模块化部署配置示例
// 顶层项目 settings.gradle.kts
include(":app", ":module:cart", ":module:product", ":module:common")
// 公共模块 build.gradle.kts(:module:common)
plugins {
id("com.android.library")
id("dagger.hilt.android.plugin")
id("kotlin-kapt")
}
android { ... }
dependencies {
implementation("androidx.hilt:hilt-navigation-fragment:1.0.0")
}
// 产品模块 build.gradle.kts(:module:product)
plugins {
id("com.android.library")
id("dagger.hilt.android.plugin")
}
dependencies {
implementation(project(":module:common"))
implementation("androidx.hilt:hilt-navigation-fragment:1.0.0")
}
推荐工具:使用
hiltViewModel() 扩展函数自动根据 Fragment/Activity 的作用域创建 ViewModel,无需手动指定 Owner。