第4章 Navigation与Activity/Fragment集成:宿主容器选择、视图层级、跨组件通信
本章聚焦 Navigation 组件在 Android 原生视图架构中的落地方式:如何选择宿主容器、组织视图层级,以及在前端碎片化场景下实现高效的跨组件通信。这是将抽象导航框架绑定到具体 Activity/Fragment 生命周期的关键环节。
4.1 模块边界与分层职责
集成后的视图架构分为三层,每层有清晰的边界与职责:
- 宿主层(Activity):作为唯一的窗口宿主,负责创建
NavHostFragment、持有全局NavController,并处理系统级事件(如返回键、配置变更)。 - 导航层(NavHostFragment):作为 Fragment 容器,管理 Fragment 的添加、替换、入栈出栈,维护回退栈,向宿主层报告导航状态变化。
- 业务层(Fragment):聚焦 UI 渲染与用户交互,通过
NavController触发导航,通过SavedStateHandle或SharedViewModel实现跨 Fragment 数据传递。
4.2 组件职责矩阵
| 组件 | 核心职责 | 边界限制 |
|---|---|---|
NavHostFragment | 承载 NavGraph,管理 Fragment 生命周期 | 不直接操作业务数据 |
NavController | 处理导航指令,维护回退栈 | 不持有任何 Fragment 引用 |
NavGraph | 定义目的地与导航规则 | 仅为配置 DSL,无运行时状态 |
Fragment | 呈现 UI,响应导航事件 | 不直接操纵其他 Fragment |
4.3 视图层级与宿主容器选择
宿主容器可以选择单 Activity + 单一 NavHostFragment(推荐)或多 Activity + 各自 NavHostFragment。对比权衡如下:
| 维度 | 单 Activity + 单 NavHost | 多 Activity + 多 NavHost |
|---|---|---|
| 视图层级 | 扁平,Fragment 栈深度易控 | 层级深,Activity 栈与 Fragment 栈叠加 |
| 跨组件通信 | 共享 ViewModel + 作用域绑定 | 需 Intent + 序列化,开销大 |
| 回退栈一致性 | NavController 统一管理 | 系统 backstack 与 Nav 回退栈冲突风险 |
| 配置变更处理 | 单一宿主,状态恢复简单 | 多宿主需分别处理 |
| 推荐场景 | 大多数中大型应用 | 模块独立分包或旧项目渐进迁移 |
若选择多 Activity 方案,务必禁用各 Activity 中 NavHostFragment 的默认回退栈,避免双重
onBackPressed 冲突。4.4 数据流 / 控制流 / 信任链路
一次典型导航事件的数据流如下:
- Fragment A 调用
findNavController().navigate(…)。 NavController解析NavGraph中的目标,触发NavHostFragment执行 Fragment 替换。- Fragment B 被创建并添加到容器,A 被加入回退栈(若设定了 pop 行为)。
- 若需要回传结果,B 在生命周期内通过
SavedStateHandle.set("key", data)写入,A 在 onResume 中通过getCurrentBackStackEntry()?.savedStateHandle?.getLiveData("key")接收。
信任链路:
Fragment → NavController → NavHostFragment → FragmentManager
所有导航变更需经 NavController 校验,不允许绕过(如直接操作 FragmentManager),以此保证状态一致性。
4.5 跨组件通信架构
官方推荐的通信模式(按适用范围排序):
- 共享 ViewModel (作用域限定在 Activity):适合兄弟 Fragment 间数据同步,生命周期由宿主控制。
- SavedStateHandle (Navigation 原生):适合跨目的地回传简单值,无需引入额外依赖。
- Navigation Result API:利用
NavBackStackEntry的 SavedStateHandle,是官方推荐的标准回传方式。 - EventBus / LiveDataBus:需谨慎使用,避免事件泄漏与生命周期错配。
// 结果回传示例(Fragment B)
val result = Bundle().apply { putString("data", "hello") }
findNavController().previousBackStackEntry?.savedStateHandle?.set("result", result)
// Fragment A 接收(推荐在 onViewCreated 中注册)
val savedStateHandle = findNavController().currentBackStackEntry?.savedStateHandle
savedStateHandle?.getLiveData<Bundle>("result")?.observe(viewLifecycleOwner) { bundle ->
// 处理回传数据
}
4.6 选型权衡关键指标
| 决策项 | 权衡点 | 推荐配置 |
|---|---|---|
| 宿主容器数量 | 1 vs N | 1(单 Activity) |
| NavHost 实例数 | 1(嵌套 vs 非嵌套) | 1(避免嵌套 NavHost 复杂度) |
| Fragment 生命周期作用域 | Activity 级别 vs NavBackStackEntry 级别 | 优先 NavBackStackEntry 级别控制 |
| 跨组件通信方式 | SharedViewModel vs SavedStateHandle | 简单数据用 SavedStateHandle,复杂用 SharedViewModel |
4.7 架构风险点与应对
- Fragment 生命周期与导航状态不同步:在
onViewCreated之后才安全调用navigate(),避免在onCreate中触发导航导致状态丢失。 - 回退栈无限增长:合理设置
popUpTo和popUpToInclusive,避免栈深度无限累积。 - 跨组件通信内存泄漏:使用
viewLifecycleOwner注册 Observer,避免引用 Activity 级别的 ViewModel 后忘记清除。 - 嵌套 NavHostFragment 的焦点混乱:避免在同一 Activity 内嵌套多个
NavHostFragment,若必须则需手动处理焦点管理。
推荐在 BaseFragment 中统一封装
findNavController() 的异常捕获,防止 Fragment 未附加时崩溃。4.8 落地步骤
- 创建宿主 Activity:在布局中声明
NavHostFragment,指定默认navGraph。 - 定义 NavGraph:用 DSL 或 XML 声明所有 Fragment 目的地及导航动作。
- 迁移 Fragment 至单 Activity:将原多 Activity 中的 Fragment 集中到同一 Graph,调整
navigate()参数。 - 统一通信层:按数据复杂度选择 SharedViewModel 或 SavedStateHandle,封装 BaseViewModel 提供作用域绑定。
- 测试回退栈行为:手动执行深度导航(A→B→C→D),验证 popUpTo 和系统返回键行为。
- 收尾:事件上报与状态持久化:将导航路径协议化,用于分析埋点或 DeepLink。
4.9 验收标准
- 导航一致性:所有 Fragment 的
NavController.navigate()均能正确触发目标创建,无方向性崩溃。 - 生命周期正确性:Fragment 的
onStart/onStop与导航事件对齐,无内存泄漏。 - 跨组件通信无丢失:回传数据在配置变更(如旋转屏幕)后仍能正确到达目标 Fragment。
- 回退栈边界:从顶层按返回键,每一步都弹出预期 Fragment,最终退出 Activity 而非黑屏。
4.10 架构关系图
图中虚线表示跨组件通信的数据流,实线表示导航控制流。注意
NavController 作为唯一管控入口,避免直接操作 FragmentManager。