第4章:模块间的状态所有权:谁创建谁拥有,谁消费谁订阅

跨模块状态交互的第一个原则就是确定 所有权边界。所有权由生命周期和创建时机决定:哪个模块负责创建 ViewModel 或状态容器,哪个模块就拥有该状态的生命周期。其他模块只能通过订阅的方式消费这些状态,而不能直接干预其创建或销毁流程。

核心定义:“谁创建谁拥有,谁消费谁订阅”是跨模块状态隔离的底线契约。越过这条底线,就会引发生命周期错配、内存泄漏和状态污染。
跨模块状态所有权与订阅模型 消费者模块(订阅者) 消费者 A(UI) 消费者 B(服务) 动作 / 意图 提供者模块(所有者) 状态容器 业务逻辑 数据层(DB/网络) 控制流(动作) 数据流(状态) 关键原则: 提供者管理状态生命周期; 消费者订阅并观察状态。

边界定义

所有权边界由 模块依赖关系 和 生命周期作用域 共同界定。模块 A 在 DI 容器中创建 ViewModel,模块 B 和 C 通过接口订阅该 ViewModel 的状态,而不是直接持有 ViewModel 的引用。

模块 A 的销毁信号发出后,所有订阅该状态的模块必须停止数据采集,否则会产生悬挂引用。

维度拥有状态(提供者)消费状态(订阅者)
生命周期责任创建 & 销毁附加 & 分离
状态突变允许(内部)禁止(只读视图)
线程安全必须保证不关心
测试策略集成测试Mock / Fake 状态

分层策略

在跨模块分层架构中,状态通常驻留在 Core / Common 层 或通过 DI 容器 进行作用域绑定。Feature 模块只能通过接口依赖关系来消费状态,而不能在本地重新创建或持有缓存副本,这就是 “提升状态(State Hoisting)” 策略在模块间的应用。

// 公共 API 层(合约)
interface UserStateProvider {
    val state: StateFlow<UserState>
}

// 实现层(由 Core 模块提供实现)
@Singleton
class UserStateProviderImpl(
    private val repository: UserRepository
) : UserStateProvider {
    override val state: StateFlow<UserState> = _state
    private val _state = MutableStateFlow(UserState())
    // ……
}

组件职责

  • 状态提供者: 负责实例化 StateFlow,暴露安全的读取接口,处理业务逻辑,并决定状态更新的时机与粒度。
  • 状态消费者: 在自身的生命周期回调中(如 onCreate 或 onStart)订阅 collect,在生命周期结束时取消订阅。消费者通过动作 / 事件反馈给提供者,而不是直接赋值。

数据流 / 控制流 / 信任链路

数据流: 状态从提供者单向向下流向消费者。消费者通过 collect 接收快照。

控制流: 动作从消费者向上流向提供者。提供者经过业务逻辑处理后,产出新状态并推送给所有订阅者。

信任链路: 提供者信任消费者不会滥用状态令牌(例如通过反射修改),消费者信任提供者不会在生命周期中途意外死亡。双方通过不变的接口契约和严格的 DI 作用域来维持这种信任。

选型权衡

选型优势劣势
StateFlow同步、挂起安全、粘性数据、跨模块友好需要协程环境,不适合纯 Java 模块
SharedFlow一次性事件(导航、Snackbar)无初始值,消费者需自行处理粘性防抖
LiveData生命周期感知、Android 原生支持不支持协程挂起,线程模型较弱
冷 Flow按需创建数据流每个订阅者独立产生数据,不适合共享状态

风险点

生命周期错配: 如果消费者模块的 Fragment 在提供者模块的 ViewModel 之前被销毁,且未在 onDestroy 中取消订阅,则会造成内存泄漏或空指针异常。
状态泄露: 消费者意外保留了外部状态容器的引用,阻碍了 GC 回收。
惊群效应: 提供者频繁发出大量状态更新,导致所有订阅者不必要地执行多次重绘或计算。推荐使用去抖(debounce)或去重(distinctUntilChanged)策略。

落地步骤

  1. 审查模块依赖图: 标识出哪些模块需要共享状态,确定生命周期先后顺序。
  2. 定义合约接口: 在公共层创建接口,如 interface UserStateProvider,并声明状态流。
  3. 实现提供者: 在 Core / 实现模块中完成状态容器和业务逻辑的注入。
  4. 作用域绑定: 在 DI 容器中使用 @Singleton 或自定义作用域进行绑定,确保提供者生命周期受控。
  5. 消费者注入: 消费者模块注入接口,并在其自身作用域内安全收集状态流。
// 步骤4 – Dagger / Hilt 绑定示例
@Module
@InstallIn(SingletonComponent::class)
abstract class StateModule {
    @Binds
    abstract fun bindUserStateProvider(
        impl: UserStateProviderImpl
    ): UserStateProvider
}

// 步骤5 – 消费者订阅
@AndroidEntryPoint
class ProfileFragment : Fragment() {
    @Inject lateinit var userProvider: UserStateProvider

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            userProvider.state
                .flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
                .collect { state -> /* 更新 UI */ }
        }
    }
}

验收标准

  • 生命周期隔离: 提供者作用域结束后,所有订阅者的回调必须自动停止。
  • 类型安全: 消费者无法直接写入提供者的状态容器(只读接口)。
  • 稳定性: 跨配置变更(如屏幕旋转)后,订阅依然正常,不抛出空指针异常。
  • 可测试性: 提供者的状态变更逻辑通过 Fake / Mock 消费者得到验证,测试覆盖率超过 80%。