Lifecycle 与 SavedState
1. 目标
本文定义宿主生命周期、生命周期感知 Flow、已提交 Android View 和 rememberSaveable 的提交/
恢复边界。
核心原则:
- 未提交的组合不能永久消费恢复值。
- 宿主保存发生在组合准备期间,也不能丢失已 claim 的值。
- 独立子组合不能共享一个扁平 Provider Key 命名空间。
- 生命周期快速切换时,同一个 Flow 最多只有一个活跃 collector。
- 已销毁宿主不能创建新的渲染会话或 SavedState 绑定。
- Renderer 拥有的 View 在事务提交前不能观察或发布外部 Owner。
- 逻辑页面不能仅因其 ViewGroup 仍 Attached 或被 Retain,就借用 Activity 的完整活跃生命周期。
- UI-only Saveable State 与恢复型业务状态不能同时拥有两个可写 Owner。
2. 宿主生命周期
ComponentActivity.setUiContent 的会话绑定到 Activity 生命周期;
Fragment.setUiContent 的会话绑定到 Fragment View Lifecycle,并在 View 销毁时释放。Activity
内容的 Lifecycle 与 SavedState Owner 都是 Activity。Fragment 内容则有意使用 Fragment View
Owner 作为 Lifecycle Owner、Fragment 作为 SavedState Owner,使 View 工作在 onDestroyView
结束,同时让兼容的 SDK State 可以跨 View 重建保留。
Activity/Fragment 入口与自动 Owner 安装属于 viewcompose-android。viewcompose-host-android
负责底层 Session、Scheduler、renderInto 与 Android SavedState 桥接,不得重新承载
Activity/Fragment 便利 API。生命周期感知收集与 ViewModel 访问继续位于各自命名的 AndroidX
集成模块。
边界规则:
- 重复调用
setUiContent会先释放旧会话。 ON_DESTROY释放会话、组合副作用、协程和平台资源。- 对
DESTROYED宿主调用setUiContent立即失败,不创建半绑定会话。 LifecycleBoundDisposer绑定到已经销毁的 owner 时立即执行释放。
3. 生命周期感知 Flow
collectAsStateWithLifecycle 只接受:
CREATEDSTARTEDRESUMED
INITIALIZED 与 DESTROYED 不能作为活跃阈值。
实现使用 repeatOnLifecycle 的串行取消/重启语义:前一个 collector 的取消和
finally 清理完成后,下一次 collector 才能进入,避免快速 STOP -> START 产生并发收集。
组合释放会取消整个结构化收集作用域。
4. 已提交 Android View 的 Owner 协同
可复用原生 View 集成使用类型安全 Android Host Adapter 管理事务所有权,并使用 AndroidX Lifecycle 集成协调 Owner。可重放的 View Create 与 Update 在 Renderer Apply 期间运行; Lifecycle 和 SavedState Binding 只从 Post-commit Hook 开始。因此被放弃或回滚的候选对象不能 观察 Owner、消费恢复的 SDK State,也不能发布 Provider。
一个 View 最多持有一个 Lifecycle Binding。首次 Attach 按 Android 事件顺序 Catch-up 到捕获 Owner 的当前 State。Owner 替换会先完成旧 View 侧的下降序列并移除 Observer,再执行新 Commit 工作与上升 Catch-up。Retained Navigation Destination 提供受限的 Destination Owner,而不是 Activity Owner,因此隐藏内容不会仅因 View 仍挂载就继续运行 Media Surface、Map 工作或 Camera Capture。Reset、最终 Release、Owner Destroy 和 Callback Failure 都执行有界、一次性的清理。
Lifecycle Adapter 只协调 View 侧事件,不控制应用拥有的播放、权限、凭据、Lifecycle State 或 SDK 对象所有权。Host 会记录 Adapter Lifecycle Mode 用于诊断,但不会自行安装 Observer。
5. rememberSaveable 恢复事务
恢复分为四步:
- 组合 prepare 时按稳定 key
claimRestored。 - claim 中的值参与恢复,但仍包含在
performSave()快照中。 - 组合 commit 后注册 provider,再提交 claim。
- 组合 abort 或新值被 abandoned 时释放 claim,后续重试仍可恢复同一值。
因此,组合异常、renderer apply 回滚、保存与渲染交错都不会提前丢失恢复值。
rememberSaveable(inputs...) 的 input 变化仍表示有意重置:旧 holder 只在提交阶段
退出,新 holder 同步接管 provider,最终保存替换后的值。
6. 子组合所有权
Host Registry 是根持久化边界,不是所有嵌套 RenderSession 的全局 Key 命名空间。创建延迟子组合
的框架容器会在父组合中 Remember 一个 State Holder。Holder 按稳定 Lazy Item、Pager Page、Tab
或 Overlay Surface 身份分别提供子 Registry。
自动与显式 rememberSaveable Key 只在收到的子 Registry 内有效。嵌套容器会在该 Registry 内继续
创建 Holder,因此所有权跟随组合树:
host owner
└── container holder
├── logical item A registry
│ └── nested container holder
└── logical item B registry
回收会关闭 Item Registry Lease,并在 Holder 中保留其 Saved Map;相同逻辑 Key 再次 Attach 时 恢复。Keyed Reorder 因而让逻辑状态跟随 Key,而不是跟随 View Holder 位置。Renderer 并发创建的 Presentation 副本可以恢复 Owner 当前 Snapshot,但不会成为第二个持久化 Owner。
Holder 本身通过父 Registry 的常规事务保存。失败父帧不能发布候选子所有权;失败子帧会保留此前 的 Provider 与恢复值 Claim。硬切原因与兼容边界参见 ADR-0010。
7. ViewModel 与逻辑页面所有权
Activity 与 Fragment 仍是 Root Owner 来源,但它们不定义每个逻辑页面的生命周期。Retained
Navigation Entry 或 Graph 的页面 Owner 由四项协同契约组成:受限的 LifecycleOwner、
SavedStateRegistryOwner、ViewModelStoreOwner,以及默认 Factory/CreationExtras 支持。
页面生命周期同时受宿主生命周期与导航可见性约束;仅有 View Attached 永远不会把隐藏内容提升到
RESUMED。
导航会投影内容 Pane 与末尾模态后缀。Covered Layer 保持可见且处于 STARTED,只有顶部 Overlay
可达到 RESUMED;Motion 在稳定前把所有可见参与者限制为 STARTED。内容与 Overlay 暴露同一个
最近 AndroidX LocalLifecycleOwner,无需导航专用 Lifecycle 或全局 Current-page API。
配置重建会保留 Navigation Host Scope Identity,并从 ViewModelScopeProvider 租用相同 Child
Store。永久 Pop 或 Graph Removal 会发送显式终止信号并只清理 Store 一次,继而交付 AndroidX
ViewModel.onCleared()。临时离开组合、Transition 重叠、Pager/Lazy 重排,以及配置变化时已销毁的
Activity,只释放 Composition Lease,不会误报逻辑删除。
普通 Retained Subtree 使用同一个 Provider Core。组合只 Remember Provider Binding 与持有引用的
Lease,从不缓存 ViewModel 实例。viewModel() 始终委托 AndroidX ViewModelProvider 在选定的
ViewModelStore 上解析。显式 Owner 优先于最近的 LocalViewModelStoreOwner;Activity 与
Fragment Host API 自动安装最近的 ViewTree Owner,而底层 renderInto 有意不安装 Owner。
恢复型业务状态属于业务 ViewModel。它通过 Owner 默认 Factory,或在 Initializer Overload 内调用
createSavedStateHandle(),获得唯一 SavedStateHandle,并向外暴露只读领域状态或操作。UI-only
展示状态继续由 rememberSaveable 持有。已删除的独立 Handle Helper 与 Holder 不存在 Runtime、
Source 或兼容路径;其 Released Capability Record 仅作为不可变删除 Impact Record 所需的历史身份
保留。
8. Android Bundle 边界
Android host 保存:
null- Bundle 可保存的平台值
- 递归
List - String-keyed
Map
恢复时会安装宿主 class loader。未知格式版本整体忽略;单个损坏 entry 被隔离, 不会阻止其余有效 entry 恢复。
瞬时系统会话不属于 SavedState:IME composition、撤销历史、进行中的手势和动画不会恢复。
拥有 Bundle Payload 的 SDK View 只在 Adapter Commit 后注册一个 Provider。其稳定 Provider Key 受最近 SavedState Owner 约束,并与 Renderer Reconciliation Identity 分离。SDK 集成拥有 Payload Schema 和正数 Format Version;框架拥有注册顺序、Restored Value 一次性消费、防御性 Bundle Copy、替换与清理。Format 不匹配或损坏的嵌套 SDK Payload 会被当作无 State,不会使其他 Provider 失效。后续 Commit 只替换 Saver,保证 Host 保存时总是读取最近已提交的 View。
9. 验证
核心回归覆盖:
- nullable、嵌套集合和自定义 Saver
- composition abort 后重试恢复
- in-flight claim 期间宿主保存
- 快速生命周期停止/重启的 collector 串行性
- destroyed owner
- 使用相同自动与显式 Key 的兄弟和嵌套子组合
- Keyed 子项回收、重排、Host 重建与并发 Presentation 副本
- 未知 Bundle 版本和单 Entry 损坏隔离
- Android View Commit 后 Lifecycle Catch-up、串行 Owner 替换与 Callback Failure
- Retained Destination Lifecycle 限制、SDK Bundle 重建、Format 隔离与 Provider 清理
- Retained ViewModel Scope 引用计数、Rollback、Recreation、Terminal Clear 与 No-resurrection
- Destination/Graph Owner 隔离、默认 Factory/CreationExtras 传播、
onCleared()、连续进程式恢复, 以及真实多 Stack 进程重建 - Lifecycle DSL 在声明与 Commit 之间发生激活或 State 变化时的对账、Owner 替换与已 Abort 替换、 失败或已 Abort Composition 的惰性,以及声明失败后的嵌套 Owner 恢复
2026-08-29 的导航 Lifecycle DSL 稳定化重新运行了 Lifecycle 模块全部 43 个 JVM 与 Robolectric 测试,零失败。此前基线包含 35 个测试;新增 8 个 Case,使可执行契约数量增加 22.9%,并直接覆盖 第 13 项的竞态与失败路径。结论是契约覆盖 improved、运行时行为 no material change:现有 Commit-aware Effect 与 Producer 实现已经满足已接受契约,因此没有新增生产声明或宿主专用 Lifecycle API。Fresh Cross-host 回归也通过了 227/227 个测试:43 个 Lifecycle、21 个 Android Aggregate、 151 个 Navigation Android 和 12 个 Preview Runner 测试。该证据不支持真机或性能结论;首次改变 Android 导航生命周期行为的阶段负责对应验证。