用途
该技能面向 Jetpack Compose 的 Navigation-Compose,目标是在目的地列表不断增长时,让类型安全导航图仍然保持可读。核心思路是用可序列化对象替代字符串路径,并按应用的功能区域拆分文件和包。
主要能力与组织方式
每个路由对象单独一个文件,归入对应区域的包,文件内只保留参数。示例:
@Serializable data object HomeDestination
@Serializable data class AlbumDestination(val browseId: String)
宿主 NavHost 只声明一次转场动画,并直接注册少量入口——文档示例中是底部栏的五个标签加上全屏播放器;其余区域各调用一次扩展函数。判断一个目的地应直接注册还是放进区域图,依据是它的进入方式:来自应用框架(chrome),还是来自另一个屏幕。
区域图是 NavGraphBuilder 上的普通扩展函数,从 entry 读取参数:
fun NavGraphBuilder.listScreenGraph(innerPadding: PaddingValues, navController: NavController) { composable<AlbumDestination> { entry -> val data = entry.toRoute<AlbumDestination>() ForceDarkContent { AlbumScreen(browseId = data.browseId, navController = navController) } } }
关键约定
这些扩展函数不是嵌套导航图。它们直接调用 composable<…>,所有目的地都平铺在同一个宿主图中,不存在 navigation<…> 调用,因此没有按区域的起始目的地、没有嵌套返回栈、也不能把 ViewModel 作用域限定到某个区域。需要这些语义就应使用真正的嵌套图;不需要时,这种写法只增加可读性,不引入额外语义。
窗口内边距、NavController、隐藏或显示底部栏的回调等跨切面值,应作为区域函数的普通参数从宿主传入,不应放进路由。理由是路由会被序列化进返回栈并在进程死亡后恢复,其中的内容必须在一小时后仍然有意义,而 lambda 做不到。
主题属于路由的属性,通过在注册处包裹屏幕施加,例如用 ForceDarkContent 包住 PlaylistScreen。包裹整个宿主会强制所有目的地使用该主题;把包装器放进屏幕内部又会让屏幕无法在不需要该主题的地方复用。文档提醒注意哪些目的地施加了该主题、哪些没有,因为这种不对称从屏幕内部看不出来。
转场只在宿主上定义一套,目的地不覆盖;一旦某个目的地使用私有转场,后续评审者就难以判断其余目的地是刻意还是遗漏,因此任何覆盖都应说明原因。
路由参数保持可序列化的原始类型,转换方式在路由的 KDoc 中注明。例如概念上是枚举的参数存字符串形式,并注明使用哪个转换函数,而不是注册自定义 NavType。属于某个路由的常量放在该路由自己的伴生对象中,紧挨它所约束的参数。
允许故意不带参数的路由,但必须写明原因。文档示例中有一个无参登录路由,其 KDoc 说明回调令牌改为传给 ViewModel;若把它放进路由,会在用户当前所在的登录屏上再压入一份登录屏。没有这条注释,空路由会被误认为疏漏。
深链接不放在目的地上:没有 composable<…>(deepLinks = …),外部链接在导航图之外解析并按应用级 intent 分发。这是需要显式决定的分岔点,避免一半链接挂在路由上、一半放在 ViewModel 里。
需要注意:路由没有对应注册项时仍能编译,因为没有东西把路由和导航图关联起来,直到某个 composable<…> 块命名它;因此只写目的地文件加导航调用可以构建成功,却在点击时失败。
验证方式
文档给出四组命令。第一组用 find 配合 grep -L 找出 navigation/destination 路径下缺少 @Serializable 的 Kotlin 文件,无输出为预期。第二组用 comm 比较声明的路由名与所有 composable<…> 注册名,无输出为预期;文档提醒先统计左侧集合数量并与文件数核对,因为用普通 object 而非 data object 声明的路由是合法的,只匹配 data class|data object 的模式会漏掉它,从而因未检查而得到“无输出”。第三组用 grep 列出 NavGraphBuilder 扩展函数与 NavHost,预期一个宿主、每个区域一个扩展函数。第四组查找带私有转场的 composable 调用,无输出表示只有宿主的一套转场。
最后进行运行时验证:深入导航、把应用切到后台、让进程被杀掉再返回,返回栈会从序列化路由重建,此时不是真正值的参数才会暴露出来,而此前不会暴露;同时观察每个区域中一个目的地的进出转场,动画与邻居不同的路由就是第四步漏掉的覆盖。
适用场景与限制
适用于采用 Navigation-Compose 类型安全路由、目的地数量持续增长、需要按区域组织代码并保持转场与参数约定一致的 Android 项目。限制在于:该方案不提供嵌套图语义,区域级 ViewModel 作用域和独立返回栈需要改用真正的嵌套图;深链接需另行处理;路由未注册只能在运行时发现,因此依赖文档中的命令与运行时检查来兜底。