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/27 7:50:21 阅读更多 →
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/26 12:45:21 阅读更多 →
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/27 8:00:38 阅读更多 →

最新新闻

机器人会拥有自主意识吗?

机器人会拥有自主意识吗?

随着人工智能技术飞速迭代,智能机器人早已走出实验室,走进大众生活。从精准应答的智能客服、灵活作业的工业机器人,到能自主对话、创作内容的AI机器人,它们的智能化程度不断突破人们的认知。很多人不禁发问:高度智能的…

2026/7/28 2:04:27 阅读更多 →
深度实战:3天掌握Dify工作流开发,打造企业级AI应用

深度实战:3天掌握Dify工作流开发,打造企业级AI应用

深度实战:3天掌握Dify工作流开发,打造企业级AI应用 【免费下载链接】Awesome-Dify-Workflow 分享一些好用的 Dify DSL 工作流程,自用、学习两相宜。 Sharing some Dify workflows. 项目地址: https://gitcode.com/GitHub_Trending/aw/Aweso…

2026/7/28 2:04:27 阅读更多 →
JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决

JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决

JSQLParser 4.x 中 IN 表达式缺少括号的问题排查与解决 问题背景 在使用 MyBatis Plus 拦截器动态拼接数据权限 SQL 时,使用 JSQLParser 构造 IN 表达式,运行后发现生成的 SQL 语法错误: -- 期望 SELECT * FROM sdk_project WHERE deleted 0…

2026/7/28 2:04:27 阅读更多 →
HarmonyOS应用开发实战:猫猫大作战-排序在下落算法中的关键作用

HarmonyOS应用开发实战:猫猫大作战-排序在下落算法中的关键作用

前言 在重力下落算法中,排序顺序至关重要——必须先处理底部的猫咪再处理顶部的,否则会出现“穿透“现象。Array.sort() 配合比较函数实现自定义排序。 本文以 GameEngine.updateCats() 中的 sort((a,b) > b.y - a.y) 为锚点,讲解排序在…

2026/7/28 2:04:27 阅读更多 →
私有化IM部署:能用只是开始,全栈信创适配等五道坎决定能否管好

私有化IM部署:能用只是开始,全栈信创适配等五道坎决定能否管好

私有化IM部署热背后:“能用”只是开始,全栈信创适配等五道坎决定能否“管好” 当越来越多的组织把即时通讯从SaaS拉回内网,私有化部署似乎成了解决数据焦虑的“标准答案”。但一个新的管理盲区正在浮现:许多团队以为只要聊天功能在…

2026/7/28 2:04:27 阅读更多 →
2026 年程序员找工作,为什么 AI 写出的代码反而成了简历的“硬伤”?

2026 年程序员找工作,为什么 AI 写出的代码反而成了简历的“硬伤”?

聊《一份看似完整的程序员就业方案,为什么投递时没效果?》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要摘要:2026 年,AI 编程工具已不是新鲜事,但能…

2026/7/28 2:03:26 阅读更多 →

日新闻

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生

告别臃肿!3步让你的暗影精灵笔记本重获新生 【免费下载链接】OmenSuperHub Control Omen laptop performance, fan speeds, and keyboard lighting, and unlock power limits. 项目地址: https://gitcode.com/gh_mirrors/om/OmenSuperHub 你是否也曾为官方Om…

2026/7/28 0:00:43 阅读更多 →
RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

RAG必踩坑!财报法规检索不准?这款开源工具让答案浮出水面,准确率飙升98.7%!

做 RAG 的人应该都踩过这个致命的坑:把几百页的财报、法规、技术手册扔给向量库,问一个具体问题,搜出来的全是沾边但没用的内容 —— 关键信息要么被硬切块拆碎了,要么藏在几十条结果的最下面。语义相似≠真正相关,这个…

2026/7/28 0:00:43 阅读更多 →
抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

抖音视频文案提取工具全指南:免费2026版、手机App、在线工具一网打尽

2026年做短视频运营,从抖音上扒文案早就不是偷偷抄笔记的事了。我刚开始做内容的时候,每天刷半小时抖音,手动把爆款视频的口播敲进备忘录,一条2分钟的视频得花十来分钟,碰到语速快的还要反复回听。后来试了一圈工具&am…

2026/7/28 0:00:43 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/27 4:33:59 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/27 6:31:56 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/27 4:01:12 阅读更多 →

月新闻