ViewModel 与状态恢复:SavedStateHandle 与 Factory
文章目录第 1 章 配置变更与进程死亡为什么需要 ViewModel原理补充踩坑动手练第 2 章 StateFlow表现层状态的现代默认原理补充踩坑动手练第 3 章 viewModelScope结构化并发与取消踩坑动手练第 4 章 SavedStateHandle进程恢复与导航参数原理补充踩坑动手练第 5 章 ViewModelFactory构造注入与测试替身踩坑动手练第 6 章 反模式与 LiveData 迁移边界动手练面试速查 · 追问链追问链 #1ViewModel 为什么能在旋转后存活 追问链 #2StateFlow 和 LiveData 新代码怎么选 追问链 #3SavedStateHandle 存什么不存什么 ⭐追问链 #4viewModelScope 取消语义 ⭐追问链 #5ViewModelFactory 测试怎么做 完整链路一句通相关推荐第 1 章 配置变更与进程死亡为什么需要 ViewModelJava 时代常见写法筛选条件、滚动位置、表单草稿写在Activity字段里。旋转屏触发onDestroy/onCreate字段归零低内存杀进程后更是全部丢失。onSaveInstanceState只能存轻量Bundle复杂对象要自己序列化且容易和 UI 逻辑缠在一起。ViewModel 的设计目标很直接在配置变更时保留 UI 相关状态生命周期长于Activity/Fragment短于应用进程。它不知道 View只暴露状态与意图Intent/Action符合 MVVM 单向数据流UDF。classOrderListViewModel(privatevalrepository:OrderRepository,):ViewModel(){privateval_uiStateMutableStateFlowOrderListUiState(OrderListUiState.Loading)valuiState:StateFlowOrderListUiState_uiState.asStateFlow()funrefresh(){viewModelScope.launch{_uiState.valueOrderListUiState.Loading runCatching{repository.getOrders()}.onSuccess{orders-_uiState.valueif(orders.isEmpty()){OrderListUiState.Empty}else{OrderListUiState.Success(orders)}}.onFailure{e-_uiState.valueOrderListUiState.Error(e.message?:加载失败)}}}}ViewModel由ViewModelStore持有同一ViewModelStoreOwner通常是NavBackStackEntry或Activity在配置变更后拿到同一实例。进程被系统回收时 ViewModel 也会销毁——那是SavedStateHandle的战场第 4 章。原理补充ComponentActivity在onRetainCustomNonConfigurationInstance链路之外通过ViewModelStoreOwner把 store 存进NonConfigurationInstances。Fragment 的viewModels()委托把 store 绑定到 Fragment 自身或 Nav Graph 的 back stack entry决定「同屏多 Fragment 是否共享状态」。踩坑把Activity/Context存进 ViewModel → 泄漏需要Application用AndroidViewModel或 DI 注入仓库。在 ViewModel 里直接持有View引用 → 同样泄漏。用GlobalScope替代viewModelScope→ 配置变更后协程不受控可能更新已销毁 UI。动手练A用一句话说明 ViewModel 比onSaveInstanceState更适合保存「订单筛选条件」的原因。D在所在工程搜索: ViewModel()确认是否有Context字段或GlobalScope。第 2 章 StateFlow表现层状态的现代默认新工程 UI 状态首选StateFlow而不是把 LiveData 当作默认方案。StateFlow是热流始终有一个当前值collect时立刻收到最新状态适合「屏幕当前长什么样」。约定私有可变 公开只读。privateval_uiStateMutableStateFlow(ExploreUiState())valuiState:StateFlowExploreUiState_uiState.asStateFlow()funonQueryChanged(query:String){_uiState.update{it.copy(queryquery)}}update原子地基于旧值生成新值避免多线程下读-改-写竞态。UI 层用data class或sealed interface表达状态机sealedinterfaceOrderListUiState{dataobjectLoading:OrderListUiStatedataobjectEmpty:OrderListUiStatedataclassSuccess(valorders:ListOrder):OrderListUiStatedataclassError(valmessage:String):OrderListUiState}Compose 侧collectAsStateWithLifecycle()View 体系用repeatOnLifecycle(STARTED)收集避免STOPPED后仍收事件浪费电量。viewLifecycleOwner.lifecycleScope.launch{viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){viewModel.uiState.collect{state-render(state)}}}一次性事件Toast、导航、Snackbar不要塞进StateFlow反复重放——用ChannelreceiveAsFlow()或显式SharedFlow(replay0)与状态流分离。原理补充StateFlow用 CAS 更新stateFlow字段collect通过AbstractSharedFlow管理订阅。value赋值等价于compareAndSet始终有值故适合 UI State。对比 LiveData无生命周期感知内建需repeatOnLifecycle但可与 Kotlin Flow 算子无缝组合。踩坑对外暴露MutableStateFlow→ 调用方可直接改状态破坏 UDF。把导航事件放在StateFlow→ 旋转屏后重复导航。在init里launch却不处理CancellationException→ 正常取消被当成错误打日志。动手练A写出private/val封装的StateFlow三行模板。B起点下面暴露了可变流改为只读classBadVm:ViewModel(){valuiStateMutableStateFlow(UiState())}第 3 章 viewModelScope结构化并发与取消viewModelScope是挂在 ViewModel 上的CoroutineScopeDispatchers.Main.immediate为默认并在ViewModel.onCleared()时cancel()。所有异步工作应落在这个作用域内配置变更不会取消正在进行的网络请求因为 ViewModel 还在只有 ViewModel 真正销毁时才统一取消。funloadDetail(id:String){viewModelScope.launch{_uiState.update{it.copy(loadingtrue)}try{valdetailrepository.getDetail(id)_uiState.update{it.copy(loadingfalse,detaildetail)}}catch(e:CancellationException){throwe// 必须再抛勿吞掉}catch(e:Exception){_uiState.update{it.copy(loadingfalse,errore.message)}}}}多个并行任务用asyncawait仍在同一viewModelScopeviewModelScope.launch{valheaderasync{repository.getHeader()}valitemsasync{repository.getItems()}_uiState.valueUiState(header.await(),items.await())}需要切换线程时在launch内withContext(Dispatchers.IO)不要把viewModelScope默认 Main 当成「只能主线程」。踩坑viewModelScope.launch(Dispatchers.IO) { _uiState.value ... }直接改 StateFlow 可以但复杂 UI 更新仍建议在 Main 收敛。子协程用GlobalScope.launch拉活后台任务 → ViewModel 清了还在跑。忘记CancellationException重抛 → 协程取消语义被破坏。动手练C为refresh()加withContext(Dispatchers.IO)调 Repository5 行骨架。AonCleared时viewModelScope会发生什么一句话。第 4 章 SavedStateHandle进程恢复与导航参数配置变更 ViewModel 能扛进程死亡后 ViewModel 实例也没了。要把小量可序列化状态写进SavedStateHandle它底层对接SavedStateRegistry与Bundle规则一致基本类型、String、Parcelable、Serializable。导航参数推荐走 SavedStateHandle而不是手写arguments?.getStringHiltViewModelclassDetailViewModelInjectconstructor(savedStateHandle:SavedStateHandle,privatevalrepository:OrderRepository,):ViewModel(){privatevalorderId:StringsavedStateHandle.getString(orderId)?:error(orderId required)privateval_uiStateMutableStateFlowDetailUiState(DetailUiState.Loading)valuiState_uiState.asStateFlow()init{load()}privatefunload(){viewModelScope.launch{valdetailrepository.getDetail(orderId)_uiState.valueDetailUiState.Success(detail)}}}用户输入的草稿可双向绑定到 handlevardraft:Stringget()savedStateHandle.getString(draft).orEmpty()set(value)savedStateHandle.set(draft,value)可选枚举解析模式与作用域函数篇呼应privatevalcategory:Category?savedStateHandle.getString(category)?.let{name-runCatching{Category.valueOf(name)}.getOrNull()}原理补充SavedStateHandle由AbstractSavedStateViewModelFactory或 Hilt 的HiltViewModelFactory注入。set的键会进入SavedStateProvider进程重建后get能读回。大数据列表、图片应走 Room/磁盘不要塞进 Bundle。踩坑把整页ListOrder塞进 SavedStateHandle →TransactionTooLargeException。导航参数名与 XML/graph 不一致 → 运行时null。只依赖 ViewModel 字段不写入 handle → 进程杀后草稿丢失。动手练B起点补全从SavedStateHandle读取filter并触发refresh(filter)的init逻辑≤8 行。D检查所在工程导航图参数是否在 ViewModel 中通过 handle 读取。第 5 章 ViewModelFactory构造注入与测试替身ViewModel需要Repository、SavedStateHandle、路由参数时不能无参构造。Hilt 新工程用HiltViewModelInject constructor为默认无 Hilt 或单元测试时要写Factory。classOrderListViewModelFactory(privatevalrepository:OrderRepository,):ViewModelProvider.Factory{Suppress(UNCHECKED_CAST)overridefunT:ViewModelcreate(modelClass:ClassT):T{if(modelClass.isAssignableFrom(OrderListViewModel::class.java)){returnOrderListViewModel(repository)asT}throwIllegalArgumentException(Unknown ViewModel:${modelClass.name})}}Fragment 中privatevalviewModel:OrderListViewModelbyviewModels{OrderListViewModelFactory(repository)}带 SavedState 的工厂用AbstractSavedStateViewModelFactoryclassDetailViewModelFactory(owner:SavedStateRegistryOwner,privatevalrepository:OrderRepository,privatevaldefaultArgs:Bundle?null,):AbstractSavedStateViewModelFactory(owner,defaultArgs){Suppress(UNCHECKED_CAST)overridefunT:ViewModelcreate(key:String,modelClass:ClassT,handle:SavedStateHandle,):TDetailViewModel(handle,repository)asT}单元测试用ViewModelProvider.NewInstanceFactory或直接OrderListViewModel(fakeRepo)配合runTestTurbine断言uiState发射序列。踩坑每个 ViewModel 在 Activity 里手写ViewModelProvider(this).get→ 应统一by viewModels()委托。Factory 里new具体 Repository → 测试无法换 Fake通过构造参数注入接口。混淆后modelClass判断失败 → 用isAssignableFrom或 Hilt 代码生成。动手练C为FakeOrderRepository写测试调用refresh()后uiState应为SuccessrunTest Turbine 骨架。Aby viewModels()与by activityViewModels()作用域差异一句话。第 6 章 反模式与 LiveData 迁移边界反模式问题改法Activity 存业务状态旋转丢失迁入 ViewModel StateFlowMutableLiveData新功能与 Flow 生态割裂新代码 StateFlow事件用 StateFlow重复消费Channel / SharedFlowViewModel 调findViewById层次坍塌UI 层 collect 渲染超大列表状态在内存OOM / 恢复失败Room PagingLiveData遗留工程维护时可与 StateFlow 互操作liveData.asFlow()、stateFlow.asLiveData()但新模块默认 StateFlow。从 Java 迁移时先把「异步 状态」从 Activity 抽到 ViewModel再把LiveData换成StateFlow通常第二步即可。动手练A列出「应放 ViewModel」vs「应放 SavedStateHandle」各两项。D 验收清单自选一屏旋转 开发者选项「不保留活动」各测一次筛选/草稿是否按设计恢复。面试速查 · 追问链追问链 #1ViewModel 为什么能在旋转后存活 标准回答≤200 字ViewModel实例由ViewModelStore持有store 在配置变更时随NonConfigurationInstances保留。新Activity/Fragment通过同一ViewModelStoreOwner取回同一对象。生命周期到ViewModel.onCleared()不是到onDestroy()。进程死亡则 store 也没了需SavedStateHandle。追问 1Fragment 的by viewModels()和by activityViewModels()答前者作用域在本 Fragment后者共享 Activity 级 store适合多 Fragment 共享同一业务状态。追问 2ViewModel 能替代onSaveInstanceState吗答配置变更可以进程杀不行。轻量可序列化状态用 SavedStateHandle大数据用本地库。追问链 #2StateFlow 和 LiveData 新代码怎么选 标准回答≤200 字新工程默认StateFlow与 Kotlin Flow、协程算子一致易于combine、flatMapLatest。需repeatOnLifecycle保安全收集。LiveData 仅遗留维护或asFlow()桥接。一次性事件不用 StateFlow防旋转重放。追问 1StateFlow和SharedFlow区别答StateFlow 必有当前值、适合状态SharedFlow 可无初始值、可配置 replay适合事件总线。追问 2为什么暴露asStateFlow()而不是MutableStateFlow答封装写权限强制 UDFUI 只读改状态只能走 ViewModel 方法。追问链 #3SavedStateHandle 存什么不存什么 ⭐标准回答≤200 字存导航参数、筛选 id、表单草稿等小而可序列化的值。不存大列表、Bitmap、整个 Domain 图。列表用 Room图片用路径或 id。底层 Bundle 有大小限制TransactionTooLargeException是常见事故。追问 1和rememberSaveableCompose关系答Compose 控件级轻量状态用rememberSaveable业务 ViewModel 状态仍走 SavedStateHandle ViewModel。追问 2Hilt 如何拿到 SavedStateHandle答HiltViewModel构造参数直接注入SavedStateHandle无需手写 Factory。追问链 #4viewModelScope 取消语义 ⭐标准回答≤200 字onCleared()触发viewModelScope.cancel()子协程收到CancellationException。配置变更不会clear ViewModel故进行中的请求可继续。须在 catch 中重抛CancellationException其余异常映射为 UiState 错误。追问 1旋转时网络请求会断吗答默认不断因 ViewModel 还在若希望随 UI 取消应用viewLifecycleOwner.lifecycleScope发请求较少用于可恢复数据。追问 2launch默认哪个 Dispatcher答viewModelScope默认Main.immediateIO 在块内withContext(Dispatchers.IO)。追问链 #5ViewModelFactory 测试怎么做 标准回答≤200 字构造注入FakeRepository直接ViewModel(fake)或自定义Factory。用runTest驱动协程turbine.testIn收集uiState断言发射序列。不依赖 Robolectric 亦可测纯逻辑。追问 1为什么不ViewModelProvider.NewInstanceFactory生产环境答无法注入依赖生产用 Hilt 或手写 Factory 传 Repository。完整链路一句通UI 发意图到 ViewModel→viewModelScope调 Repository→MutableStateFlow私有、asStateFlow公开→repeatOnLifecycle收集→导航/草稿进 SavedStateHandle→构造注入靠 Factory 或 Hilt。相关推荐Kotlin 语法与空安全Android 开发第一课Kotlin 作用域函数let/apply 工程选型

相关新闻

Neon autoscaling vm-builder 代码流程分析

Neon autoscaling vm-builder 代码流程分析

一、工具定位vm-builder 是一个 Go CLI 工具,核心功能:将任意 Docker 镜像转换为可在 QEMU VM 中运行的磁盘镜像(qcow2)。它把容器化应用"打包"成一个完整的虚拟机环境,使用 busybox init 作为初始化系统&am…

2026/7/21 19:42:43 阅读更多 →
Neon vm-monitor机制

Neon vm-monitor机制

核心逻辑1. 读的文件:/sys/fs/cgroup/neon-postgres/memory.stat2. 取的字段:active_anon inactive_anon(cgroup.rs:168)3. 不是瞬时值,而是最近 5 个样本(500ms)的平均值(cgroup.r…

2026/7/21 19:42:43 阅读更多 →
Neon autoscaler-agent

Neon autoscaler-agent

通过 HTTP 轮询 VM 内部的 Vector 进程(Prometheus 格式 metrics),具体链路:VM 内部 Vector (host_metrics 源, 每秒采集) →暴露 /metrics 端点 (默认 9100 端口) →autoscaler-agent 每 5 秒 HTTP GET 拉取 →用 prometheus exp…

2026/7/21 19:42:43 阅读更多 →

最新新闻

DBPanel 1.0.1 版本发布:聚焦安全管理与操作优化,提升运维体验

DBPanel 1.0.1 版本发布:聚焦安全管理与操作优化,提升运维体验

DBPanel 1.0.1 版本正式发布,此次更新聚焦数据库安全管理、网站文件批量操作等三方面,同时优化多项功能,提升管理员运维体验。 聚焦三大核心更新 本次版本更新聚焦数据库安全管理、网站文件批量操作以及部署安装链路补强。在数据库安全管理方…

2026/7/21 23:03:50 阅读更多 →
数据集成平台哪家好?带你看懂ETLCloud和帆软的不同

数据集成平台哪家好?带你看懂ETLCloud和帆软的不同

数字化转型进入深水区,数据互通与流转效率直接影响企业数据价值释放。在数据平台建设项目中,数据集成作为上游基础环节,平台选型一直是IT团队重点研讨议题。当前国产赛道中,ETLCloud与帆软FineDataLink(FDL&#xff09…

2026/7/21 23:03:50 阅读更多 →
Java反应式编程核心原理与实践指南

Java反应式编程核心原理与实践指南

1. 反应式编程的本质与价值在Java生态中,反应式编程(Reactive Programming)正逐渐从前沿技术转变为必备技能。这种编程范式最核心的特征是"数据流"和"异步非阻塞",就像自来水厂与用户的关系——自来水厂&…

2026/7/21 23:03:50 阅读更多 →
微信小程序商城全栈开发:从架构到支付实战

微信小程序商城全栈开发:从架构到支付实战

1. 项目概述:微信小程序商城全栈开发实战微信小程序商城全栈开发是当前移动电商领域的热门技术方向。作为一名全栈开发者,我最近完成了一个从零到一的微信小程序商城项目,涵盖了前后端完整技术栈。这个项目不仅实现了商品展示、购物车、订单支…

2026/7/21 23:03:50 阅读更多 →
MCP迎最激进更新:无状态化变革带来新利好,也暗藏五大攻击面?

MCP迎最激进更新:无状态化变革带来新利好,也暗藏五大攻击面?

协议关键元素调整Model Context Protocol(MCP)将推出自诞生以来最激进的一次更新,RC从5月21日锁定,经10周SDK验证和社区反馈,最终版规范于7月28日定稿。此次更新由六个SEP协同完成,目标是干掉协议层的状态&…

2026/7/21 23:03:50 阅读更多 →
三步根治:定时任务重复执行、任务堆积核心方案

三步根治:定时任务重复执行、任务堆积核心方案

定时任务是后台系统不可或缺的核心基础功能,广泛应用于订单超时关闭、未支付订单自动取消、过期优惠券作废、日志定时清理、业务数据定时同步、财务数据日终统计、用户活跃度统计、消息定时推送、缓存定时刷新、死数据定时清理等关键业务场景,贯穿整个后…

2026/7/21 23:02:50 阅读更多 →

日新闻

Octane Render与C4D汉化版安装与优化指南

Octane Render与C4D汉化版安装与优化指南

1. Octane Render与C4D的黄金组合:为什么选择这个方案?在三维创作领域,渲染器的选择往往决定了作品的最终呈现质量和工作效率。作为Cinema 4D(C4D)用户,Octane Render的GPU加速特性与实时预览功能&#xff…

2026/7/21 0:00:19 阅读更多 →
GPMC接口设计:异步/同步模式与多路复用配置实战

GPMC接口设计:异步/同步模式与多路复用配置实战

1. GPMC接口设计:从硬件连接到软件配置的全局视角在嵌入式系统开发中,尤其是基于TI Sitara系列如AM263x这类高性能微控制器的项目里,外部存储器的扩展几乎是绕不开的一环。无论是存放大量非易失性代码的NOR Flash,还是作为高速数据…

2026/7/21 0:00:19 阅读更多 →
UE5 GAS框架下RPG被动技能系统:从核心原理到实战实现

UE5 GAS框架下RPG被动技能系统:从核心原理到实战实现

1. 项目概述:UE5 GAS RPG被动技能的核心价值在UE5里用GAS(Gameplay Ability System)做RPG游戏,主动技能像是你手里的武器,按一下打一下,逻辑直接,反馈也快。但被动技能,它更像是你身…

2026/7/21 0:00:19 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 8:48:31 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 5:34:47 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 8:25:39 阅读更多 →

月新闻