跳到主要内容

导航运行时架构

1. 所有权边界​

ViewCompose 导航使用 Activity 或 Window 作为最外层 Android Host,但 Destination 是框架持有 的页面,而不是 Activity 或 Fragment。必需 Runtime 由两个已发布产物持有,另有一个可选 Serialization Integration:

  • viewcompose-navigation-core 持有平台无关的 Route、Graph、保留栈、事务、Lifecycle Plan、 结构化 URI/action/MIME Deep Link 匹配器和 Pane Scene 模型;
  • viewcompose-navigation-android 持有 Destination 与 Graph 的 Android Owner、子 RenderSession、原生 View 展示、SavedState 编码、Intent 适配、系统与 Predictive Back, 以及视觉 Motion。
  • viewcompose-navigation-kotlinx-serialization 可选地从受支持的 Kotlinx Serializer Descriptor 派生 Core Spec,但不参与 Stack 或 Host Ownership。

这一分层让状态机不依赖 Android 所有权,同时让原生 Host 在唯一位置协调栈状态、渲染、 Lifecycle 和 View 层级变化。

NavRouteSpec<T> 是位于 Core 边界的应用自有 Adapter,而不是第二套导航模型。稳定名称用于 Graph 声明,Encoder 生成封闭的 NavValue 参数,Decoder 从 Entry 重建应用值。Android 类型化 命令会在进入 Host 事务前编码。Graph 不保留 Codec Callback,Snapshot 不保留应用对象,String Route 仍是互操作和恢复边界。

2. 事务边界​

导航采用两阶段操作。Core prepare 计算不可变候选状态和 Entry Mutation,但不会发布。 Android Host 准备 Destination Owner 和子 RenderSession,在暂存原生容器中完成渲染,随后才 提交 Core 事务。准备失败会回滚候选,并保留旧栈、可见 Scene 和 Owner。

一个 Controller 同时只能存在一个已准备事务。渲染、Lifecycle 移动或视觉 Motion 期间收到的 重入命令,会在当前操作到达终态后串行执行。因此 NavResult.Queued 表示等待中的已接受任务, 而不是已提交完成。

栈提交后,视觉 Motion 可以完成、取消或重定向,但不能撤销应用状态。所有视觉终态都会收敛 到已提交目标。提交后 Effect 失败会以 stackCommitted = true 上报;Host 不会假装旧栈仍是 权威状态。

页面结果属于其 Pop 事务。Core 只定向仍存活的 after.top;Android 在提交后写入该 Entry 的 可保存 FIFO Inbox,Destination DSL 在 RESUMED 时消费。不存在全局总线、任意 Entry 寻址或 第二套页面 Lifecycle 状态机。

3. 统一 Execution Plan​

Navigation Core 会把每次稳定态协调、已提交转场或 Predictive Preview 归约为一份不可变 NavExecutionPlan。三个入口分别表达调用方所处的时刻,但共享同一 Reducer 实现与同一输出词汇。 这份 Plan 是 Before/After Stack、Scene 与 Layer Order、Lifecycle Target、Presentation 的准备、 刷新、保留、淘汰与销毁、Render 暂停、Input/Accessibility/Focus Ownership、系统 Back Ownership、 Rollback 与终止清理的唯一决策来源。

Android AndroidNavExecutionPlanExecutor 按固定边界解释 Plan:Stack Commit 前准备或刷新 Candidate;随后发布 Presentation 与 Interaction;协调 Destination Context 和 Owner Lifecycle; 暂停 Outgoing Render;最后执行安全淘汰。永久移除仍属于终态清理,使 Exiting View 能完成 Motion 后再销毁 Owner。当 Plan 判定 Destination 不拥有输入时,其容器会消费 Touch、Generic Motion 与 Key Input、阻止后代获得 Focus,并退出 Accessibility Tree。Host Back Adapter 也读取同一 Plan, 不会再查询一套并行 Stack 规则。

Reducer 保持纯函数和平台无关;Executor 持有 Android Effect,不得根据 View Visibility 或 Attachment 另行推导策略。Commit 前准备失败只回滚 Plan 指定的 Candidate Presentation 与 Owner, 不会发布 Candidate Stack 或 Destination Context。Stack Commit 后的失败保留已提交目标,并使用 Plan 指定的终态清理。

4. Destination 与 Graph 身份​

每个 Destination Entry 都持有稳定的 Route Identity、Lifecycle、SavedStateRegistry Namespace、 ViewCompose Saveable-state Namespace,以及一个 Keyed ViewModelStore Lease。其子 RenderSession 和原生 View Tree 是可选展示,而不是逻辑 Owner 的组成部分。Store 由共享 Lifecycle 2.11 ViewModelScopeProvider 分配,而不是导航专用 Map。连续两次 Push 相同 Route 会产生两个 Entry 身份。隐藏的保留 Entry 即使没有展示,也会继续保持身份和状态。

NavPresentationRetentionPolicy 只控制展示资源。默认 DisposeWhenHidden 会在转场稳定后释放 所有完全隐藏的展示;RetainAll 是显式的无界选择;Bounded 以正数上限保留确定性的“最久未 隐藏”集合。可见 Scene Entry 和转场参与者永远不会成为淘汰候选。新可见 Entry 若没有展示,会先在隐藏 候选容器中完成 Render、Stage 与 Commit,再改变 Scene;失败会释放所有候选并保留此前 Stack 与 Scene。永久移除始终先释放展示,再销毁 Owner 并清理 ViewModel。

API 33 合成对比据此选择有界默认策略:原生保留量与 PSS 改善,同步重建回退,短时稳定帧样本为 no material change。精确结果与局限保留在 已归档计划中。

Entry Owner 还会保留一个 NavDestinationContext。Destination DSL 通过 LocalNavDestinationContext 读取它;嵌套 Host 会为 Child Entry 覆盖该 Local,结束后恢复父级 Holder。其可观察 NavDestinationPresentation 就是 Lifecycle 规划使用的 Core NavSceneEntry,不是 Android 层重建的数据。捕获 Local 得到的是 Holder,因此粗粒度 Visibility、Interaction、Transition、Pane 或 Layer 变化在原生 Presentation 被释放、重建后仍可 观察。永久移除会停止更新并销毁 Entry Lifecycle;不存在进程级 Current Page Registry。

Presentation 观察与资源激活被刻意拆开。AndroidX Lifecycle 是唯一的资源阈值 API。Destination Context 用于粗粒度布局和行为决策,普通转场与 Predictive Back 的连续 Progress 只留在 Motion Executor 中,不进入该可观察状态。因此普通内容只会因语义 Scene 变化失效,不会因动画每一帧失效。

每个嵌套 Graph 实例都有独立 NavGraphOwner 身份。同一 Graph 实例的后代共享 Lifecycle、 SavedState 和 ViewModel,直到最后一个保留后代被移除。以后再次进入同名 Graph Route 会创建 新 Owner。只有渲染 Destination 内容时才能访问从根到叶的 Graph 链,不能借此在活动 Host 之外制造所有权。

最近的父级 ViewModelStore Owner 是必需边界,并提供默认 Factory 和 CreationExtras。子导航 Owner 只替换 Store Owner、SavedState Owner 以及 Route 或 Graph 参数。Controller 保存的 Host Scope 身份会把全部 Child Store 命名到该父级之下。父级 Owner 身份变化会重建原生 Host,防止 保留 Entry 混用两套 Provider 契约。

Destination 或 Graph Scope 的业务 ViewModel 通过构造器与 Owner 默认 Factory 获取 SavedStateHandle,也可以在 viewModel Initializer 内调用 createSavedStateHandle()。Navigation 提供 Owner Namespace 与参数,但不创建独立的 Handle-only ViewModel。UI 专属值使用 rememberSaveable,恢复型业务值使用单个 ViewModel 持有的可变 Flow,因此页面只有一个写入者和 一条恢复路径。

Lifecycle 终止和 Store 终止按顺序执行,但不是同一事件。永久移除 Entry 或 Graph 时先请求终态 清理,再发送 ON_DESTROY;活跃 Lease 会把物理 ViewModel Clear 延迟到 Owner 销毁并关闭 Lease 之后。正常移除 Host 会清理整个 Provider。父 Lifecycle 已到 DESTROYED 后释放 Host,则只关闭 展示 Owner 而不终止 Store,使配置重建后的 Host 能用同一父 Store 和已保存 Scope 身份重新租用 既有 ViewModel;完成中的父级仍是最终清理边界。

5. Lifecycle 投影​

Navigation Core 持有一份不可变 NavScene。每个 Destination Projection 都携带 Presence、 Visibility、Interaction、粗粒度 Transition Phase、Pane Role 与 Content/Overlay Layer Role。 Planner 通过一条规则推导 Lifecycle,而不再依赖彼此独立的 ID Set:

effective destination lifecycle = min(host cap, scene cap, entry cap)

已接受的目标如下:

角色目标状态
可交互的稳定 Destination 及其 Graph 路径RESUMED
被覆盖的内容 Pane 或下层模态 OverlaySTARTED
可见的转场参与者STARTED
仍保留退出展示的已 Pop DestinationCREATED
隐藏的保留 Destination 或 GraphCREATED
提交前已准备的候选不高于 CREATED
永久移除的 Destination 或 GraphDESTROYED

生命周期先向下再向上变更,因此单 Pane Host 不会短暂拥有两个 RESUMED Destination。通过 校验的多 Pane Scene 可以有意让多个叶子 Destination 及其共享 Graph 路径进入 RESUMED。 Graph Owner 取所有后代中的最高有效状态,Android 仍按 Child-down、Parent-up 顺序应用状态。 已销毁的 Entry 和 Graph 身份不能重新引入。

Android Coordinator 会在普通转场或 Predictive 转场开始时冻结恰好一份语义 Scene,并在 Owner 协调和 Host Lifecycle 变化时复用它。所有可见 Entry 在稳定前都不可交互,且不高于 STARTED。 已 Pop 的离场 Entry 会标记为 Exiting,在 View 仍用于 Motion 时限制为 CREATED;随后先释放 展示,再销毁 Owner。Predictive 取消会恢复手势开始时的 Settled Scene;提交则把相同页面交给 普通 Pop 转场,直到终态稳定后才把进入 Entry 提升到 RESUMED。自适应 Pane 使用同一规则, 因此 Scene 变化期间不会有 Pane 提前 Resume。

NavSceneLayout 把 Stack 划分为内容 Pane 与末尾 Overlay 后缀。Covered Layer 保持可见且处于 STARTED;只有顶部 Overlay 拥有 Input、Accessibility 与 RESUMED。Android 使用一个全宿主模态 边界,并复用 Session、Owner、Result、Restore、Back 与 Cleanup。模态 Motion 只移动 Overlay, 且禁止跨 Layer Shared Matching。

6. 恢复边界​

remember 的 Controller 会持久化已提交栈、Route 参数、Destination 与 Graph 身份、选择历史、 私有 Host Scope 身份、Destination 与 Graph 的 SavedStateRegistry Bundle,以及 ViewCompose Saveable 值。它不会序列化 View、RenderSession、LifecycleRegistry 实例、ViewModelStore 内容、 待处理事务或运行中动画。首次连接和恢复连接会为所有保留 Entry 创建 Owner,但只物化当前 Content-and-overlay Scene Layout;隐藏目的地内容不会急切执行。Scene Strategy 会根据恢复后的 Stack 和当前宽度重新计算。配置重建可以通过父 Store 保留活跃 ViewModel;进程重建 则会根据恢复的 Owner 状态创建新实例。

恢复会校验格式限制、栈配置、Route 是否存在、叶子解析和 Graph 层级。不兼容或格式错误的 状态会被丢弃,改用配置的初始状态。紧邻的 Version 4 格式会通过分配新 Host Scope 身份完成迁移; 未知的新格式仍会失败关闭。这些规则可以防止应用升级后把旧 SavedState 或 ViewModel Namespace 绑定到另一个 Destination。

7. 返回与视觉 Motion​

只有活动 Controller 可以消费返回时,系统返回才会参与。Predictive Back 在已提交 Entry 上 创建预览,但不改变 Core 栈。取消时恢复稳定 Scene,完成时执行普通 Pop 事务。Detach、禁用 Back 或销毁 Host 都会取消未结束的预览,因为平台 Dispatcher 可能不再提供终态回调。 Preview 参与者保持在 STARTED;提交后,被 Pop 的离场 Entry 会在退出展示释放前进入 CREATED。

处于 STARTED 时,Android Host 向最近的 View-tree NavigationEvent Owner 注册;仅在无直接 Owner 时使用 Activity Back。两条互斥路径共用状态机,根节点禁用 Handler;Stop、Detach、禁用、Owner 变化或销毁均先取消再注销,并抑制已取消手势迟到的终态。不会合成 Forward History。

NavTransitionSpec 和 Shared Content 捕获只是展示策略。它们在提交后作用于已经拥有的 Destination Root,不持有页面或 Session,也不能接收输入或无障碍焦点。捕获失败只降级对应 视觉配对,不改变导航状态。

8. 证据与验证​

Core 测试覆盖事务、栈、Graph、深层链接、Lifecycle 与 Scene。Android 测试覆盖回滚、Owner Identity、 SavedState、队列命令、转场与 Back;Aggregate Host 与真机测试补充真实 Activity/Fragment Ownership、 View Motion 与 DSL Lifecycle 观察。可编译的导航教程和 可上线 Host 指南负责公开用法与人工验收路径。

运行 ./gradlew :viewcompose-navigation-core:test :viewcompose-navigation-android:testDebugUnitTest 执行确定性架构测试。只有指南中的真实返回、重建、Predictive Back 和失败路径也通过后,才能 接受设备行为。

已接受的测试增量、真机结果、局限与最终处置均在 已归档导航计划中解释; 原始测试输出本身不会改变这些契约。