2. MVVM架构回顾
MVVM(Model-View-ViewModel)作为Android生态中最广泛采用的架构模式,其核心价值在于职责分离与可测试性。在引入 State Hoisting 之前,先重新厘清三层的边界、数据绑定机制以及生命周期感知能力——这些是后续状态提升设计的基石。
2.1 三层职责与边界
| 层级 | 核心职责 | 依赖方向 | 关键约束 |
|---|---|---|---|
| View | 渲染UI、捕捉用户输入、委托事件给ViewModel | 依赖ViewModel(通过观察者模式) | 不含业务逻辑,不直接持有Model引用 |
| ViewModel | 管理UI状态、处理业务逻辑(通过UseCase)、协调数据流 | 依赖Model层(Repository/DataSource) | 不持有Activity/Fragment引用,不感知View具体实现 |
| Model | 数据源封装(本地/远程/缓存)、数据转换、实体定义 | 无向上依赖 | 纯数据层,不包含UI逻辑,可独立于Android框架测试 |
边界规则:View 永远不能直接访问 Model;ViewModel 必须通过抽象接口(如 Repository 协议)与 Model 通信。违反此规则将导致耦合扩散,破坏架构可测试性。
2.2 数据绑定机制
MVVM 的“绑定”分为两类:单向数据流(State → UI)与事件回调(UI → ViewModel)。现代 Android 推荐使用 Jetpack Compose 或 ViewBinding + LiveData/Flow 实现。
- 单向绑定(观察):ViewModel 暴露
StateFlow或LiveData,View 订阅并自动更新。 - 反向通信(事件):View 调用 ViewModel 的方法,ViewModel 更新状态后通知 View。
// ViewModel 暴露不可变状态
class TimerViewModel : ViewModel() {
private val _seconds = MutableStateFlow(0)
val seconds: StateFlow<Int> = _seconds.asStateFlow()
fun startTimer() {
viewModelScope.launch {
while (true) {
delay(1000)
_seconds.value++
}
}
}
}
// Compose UI 观察
@Composable
fun TimerScreen(viewModel: TimerViewModel) {
val seconds by viewModel.seconds.collectAsState()
Text("Elapsed: $seconds s")
}
选型权衡:DataBinding 适用于传统 XML 布局,但编译期生成代码调试困难;Compose 具备更好的组合性和状态管理,但要求全量迁移。混合使用时注意 ViewModel 状态必须统一通过 StateFlow 暴露,避免两套绑定逻辑冲突。
2.3 生命周期感知
ViewModel 天然感知 Activity/Fragment 生命周期:onCleared() 在拥有者销毁时调用,自动取消协程。LiveData 或 Flow 配合 lifecycleScope 确保观察在非活跃状态下暂停,避免内存泄漏和无效更新。
| 机制 | 生命周期感知方式 | 风险点 |
|---|---|---|
| ViewModel + viewModelScope | 自动跟随 ViewModelStoreOwner 销毁 | 小心在非 ViewModel 作用域(如 Application)中创建 ViewModel |
| LiveData.observe(lifecycleOwner) | 仅在前台时发送更新 | 重复订阅、丢失粘性事件需手动处理 |
| Flow.collect(lifecycle.repeatOnLifecycle) | 生命周期恢复时重新收集 | 需要正确处理取消和重连,避免重复发射 |
常见陷阱:在 ViewModel 中直接持有 Context 或 View 实例将破坏生命周期感知,导致内存泄漏。务必通过 Application 级别的 Context(如 AndroidViewModel)或使用 SavedStateHandle 传递必要参数。
2.4 架构示意图:三层协作与数据流
2.5 风险点与落地检查清单
- 过度绑定:View 中直接使用 DataBinding 表达式调用 ViewModel 方法,导致测试时需模拟整个 DataBinding 环境。应强制所有事件触发走普通函数调用。
- 状态提升不彻底:部分状态仍保存在 View 的本地变量中,绕过 ViewModel。可使用 Lint 规则或 code review 强制所有 UI 状态来源于 ViewModel。
- 生命周期耦合:ViewModel 中持有 Fragment 的
LifecycleOwner引用,造成泄漏。应完全避免传递任何 View 相关对象。 - 信任链路:ViewModel 不应信任 View 传递的原始事件,应进行输入校验;Model 层数据不应被 ViewModel 直接篡改,必须通过不可变对象映射。
验收标准:
- 所有 UI 状态均声明为
StateFlow/LiveData,且 View 只订阅一次。 - ViewModel 单元测试可独立运行(不依赖 Android 环境)。
- 生命周期回放(旋转屏幕)后状态自动恢复,不丢失。