前言
前面课程里面我们使用 remember / rememberSaveable 维护页面状态。
但是remember只能保存在Compose重组阶段;屏幕旋转(Activity重建)的时候remember会丢失数据(rememberSaveable有大小限制,不适合大量业务数据)。
ViewModel专门用来存放页面业务数据;生命周期独立于Activity重建;屏幕旋转数据不会丢失;同时配合Compose可以实现状态驱动UI。
前置依赖(module build.gradle)
dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2" implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.2" }知识点清单
- ViewModel生命周期特点:屏幕旋转不会销毁;Activity真正销毁时才销毁
- viewModel() 获取ViewModel实例;不要自己new
- MutableStateFlow + StateFlow 保存可观察业务数据
- compose中使用collectAsStateWithLifecycle收集Flow成为界面状态
- ViewModel职责:存放业务状态、处理简单业务逻辑;不要持有Compose环境引用,不要持有Context
完整示例源码
importandroidx.lifecycle.ViewModelimportandroidx.lifecycle.viewModelScopeimportkotlinx.coroutines.flow.MutableStateFlowimportkotlinx.coroutines.flow.StateFlowimportkotlinx.coroutines.launchimportandroidx.compose.runtime.Composableimportandroidx.compose.runtime.collectAsStateWithLifecycleimportandroidx.compose.runtime.getValueimportandroidx.compose.ui.Modifierimportandroidx.compose.foundation.layout.Arrangementimportandroidx.compose.foundation.layout.Columnimportandroidx.compose.foundation.layout.fillMaxSizeimportandroidx.compose.material3.Buttonimportandroidx.compose.material3.Scaffoldimportandroidx.compose.material3.TextclassCounterViewModel:ViewModel(){//可变内部数据流,私有privateval_countFlow=MutableStateFlow(0)//对外暴露只读StateFlowvalcountFlow:StateFlow<Int>=_countFlowfunincrease(){viewModelScope.launch{_countFlow.value+=1}}fundecrease(){viewModelScope.launch{_countFlow.value-=1}}}@ComposablefunCounterVmPage(){//获取ViewModel实例,不要手动new CounterViewModel()valvm:CounterViewModel=androidx.lifecycle.viewmodel.compose.viewModel()//在Compose当中收集Flow,感知生命周期valcountbyvm.countFlow.collectAsStateWithLifecycle()Scaffold{innerPadding->Column(modifier=Modifier.fillMaxSize(),verticalArrangement=Arrangement.Center,horizontalAlignment=androidx.compose.ui.Alignment.CenterHorizontally){Text(text="计数值:$count",fontSize=androidx.compose.ui.unit.sp(22))Button(onClick={vm.increase()}){Text("+ 增加")}Button(onClick={vm.decrease()}){Text("- 减少")}}}}逐行解读
- ViewModel生命周期核心特性
ViewModel对象创建之后;当屏幕发生旋转(Activity销毁重建),ViewModel实例保留不销毁;只有当页面真正finish、Activity彻底关闭的时候ViewModel才会执行clear()销毁。
适合保存比较重的业务数据、网络请求结果;弥补rememberSaveable容量有限的短板。
- 获取ViewModel实例
val vm:CounterViewModel = viewModel()
❗禁止手动 new CounterViewModel();交由系统ViewModelProvider管理实例;才能拥有特殊生命周期。 - MutableStateFlow / StateFlow
- MutableStateFlow :可变,ViewModel内部私有修改;
- StateFlow 只读对外暴露;遵循单向数据流;禁止UI层直接修改ViewModel内部可变Flow;只能调用ViewModel里面定义的函数来变更数据。
- viewModelScope
ViewModel自带协程作用域 viewModelScope ;当ViewModel销毁的时候,内部所有协程会自动取消;不用担心协程内存泄漏;网络请求、延时任务优先写在viewModelScope.launch{}里面。 - collectAsStateWithLifecycle()
收集StateFlow,并且感知Android页面生命周期;页面进入后台暂停收集;回到前台恢复收集;优先使用这个,不要直接使用collectAsState();减少后台无谓重组,节约性能。 - ViewModel编码约束(硬性)
ViewModel里面不要保存Activity、Compose@Composable引用;尽量不要持有Context;如果确实需要上下文,请使用AndroidViewModel。
ViewModel只负责业务逻辑和状态;不直接操作UI。UI层Compose组件只负责渲染,调用vm的方法触发业务变更。
容易踩坑
- ❌ 手动new ViewModel实例;直接丧失ViewModel特殊生命周期,旋转页面数据丢失。
- ❌ UI层直接修改 _countFlow.value ;破坏封装;私有可变Flow只能在ViewModel内部修改。
- ❌ 在ViewModel直接保存Activity引用,极易发生内存泄漏。
- ❌ 使用collectAsState而不是collectAsStateWithLifecycle;页面后台依然持续接收数据,不必要重组,耗电。
- ✅ 单向数据流:UI调用ViewModel的方法;ViewModel修改Flow;Flow驱动UI刷新。
多元化习题(每道题后紧跟答案+解读)
习题1【基础‑改错题】
题目:下面创建ViewModel写法错误,请修正
valvm=CounterViewModel()答案:
valvm:CounterViewModel=androidx.lifecycle.viewmodel.compose.viewModel()解读:
必须使用compose提供的viewModel()函数,由框架帮我们获取实例;框架管理ViewModel生命周期,屏幕旋转保留数据。自己new出来就是普通类对象,没有ViewModel的生命周期能力。
习题2【基础‑代码修改题】
题目:ViewModel增加reset清零方法,点击之后计数器重置为0;写出ViewModel里面新增函数。
答案:
funreset(){viewModelScope.launch{_countFlow.value=0}}解读:
在viewModelScope协程块里面修改MutableStateFlow的value;UI层Compose只调用vm.reset(),不需要直接操作Flow。
习题3【进阶‑补充代码题】
题目:说明collectAsStateWithLifecycle的优势;为什么优先选用而不是collectAsState。
答案:
collectAsStateWithLifecycle会结合Android生命周期;当App页面进入后台(页面不可见),暂停收集数据流;页面重新回到前台之后恢复收集;减少后台UI不必要重组,节省CPU和电量;collectAsState不管页面是否可见始终持续接收数据。
解读:
在Compose+Flow开发中,这是官方建议的最佳实践。
习题4【综合‑逻辑应用题】
题目:rememberSaveable 和 ViewModel 都可以解决屏幕旋转数据丢失;二者分别适合什么场景?
答案:
rememberSaveable:适合少量简单轻量状态(开关标记,输入框简短文本);有数据大小限制;数据会序列化保存到Bundle。
ViewModel:适合较重业务数据;列表数据、网络加载结果;没有大小限制;依靠对象内存保存;进程被杀之后数据依然会丢失。
解读:
进程彻底杀掉之后,ViewModel内存数据一样丢失;这种场景依然需要本地持久化(数据库、SP)。
习题5【概念简答题】
题目:什么是单向数据流(ViewModel‑Compose)?
答案:
UI层不直接修改业务状态;UI触发事件调用ViewModel内部业务函数;ViewModel内部修改StateFlow;Flow发出新的值;驱动上层Compose UI重组刷新界面;数据单向流动。
解读:
单向数据流是现代声明式UI开发重要原则;减少状态随意修改带来的难以复现bug。
本节课知识点总结
- ViewModel生命周期独立;屏幕旋转Activity重建时ViewModel对象保留;Activity真正销毁才调用clear()销毁。
- 使用 viewModel() 获取实例,禁止手动new;viewModelScope提供自动管理的协程作用域。
- MutableStateFlow私有,对外暴露只读StateFlow;单向数据流;UI调用ViewModel函数修改状态。
- compose收集Flow优先 collectAsStateWithLifecycle() ,感知生命周期,优化后台性能。
- ViewModel禁止持有Activity引用,防止内存泄漏;ViewModel负责业务;Compose负责渲染UI。
下一课预告:进阶第4课:ViewModel结合Navigation导航;不同页面之间共享ViewModel