composable() — 是 Navigation Compose 库的一个函数,它在 NavHost 中注册一个屏幕并将 URL 路由与 Compose 布局关联起来。当导航到达指定路由时,Jetpack Compose 会调用相应的 composable 函数并将其显示为当前屏幕。与 FragmentManager 或基于 Intent 的导航不同,composable() 在单个 Activity 级别工作,并完全通过 Kotlin DSL 管理。根据 Android Developers (2025) 的数据,超过 73% 的基于 Jetpack Compose 构建的现代 Android 应用程序都使用 Navigation Compose 来组织屏幕切换。
要点
composable() — 是 NavHost 对象的扩展函数。Kotlin DSL 允许在 NavHost 块内调用它来声明式地描述应用程序的所有屏幕。每次调用都会在导航图中创建一个条目,将字符串路由与 composable 函数关联起来。当用户导航到特定路由时,NavHost 会显示相应的 composable 作为当前屏幕,隐藏上一个屏幕。
Navigation Compose 库由 Google 于 2021 年推出,作为 Jetpack Compose 的基于 Fragment 的导航的替代方案。主要优势——与 Compose 范式完全兼容:composable() 与其他 Compose 组件在相同的生命周期内工作,无需 FragmentManager 或事务。这消除了与 Fragment 和 Compose 生命周期不匹配相关的一类错误。
每个 composable() 接受一个字符串路由和一个 lambda 函数,该函数接收 NavBackStackEntry 对象并返回 Composable UI。在 lambda 内部,可以通过作用域中的 navController 调用来访问 NavController,从而可以组织到其他屏幕的导航。这种架构使导航变得明确且可预测。
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen(
onNavigateToProfile = {
navController.navigate("profile")
}
)
}
composable("profile") {
ProfileScreen(
onBack = { navController.popBackStack() }
)
}
}
}
每次 composable() 调用都会在 NavHost 的内部图中创建一个具有唯一路由标识符的节点。当 NavController 执行 navigate() 时,库会将请求的路由与所有已注册的 composable 节点进行比较并找到匹配项。匹配后,会创建一个 NavBackStackEntry,将其放置在导航堆栈上,并启动 UI 组合。
composable() 的内部实现使用 懒加载 机制:屏幕组合仅在首次导航到此路由时发生。这意味着用户从未导航到的屏幕不会占用内存,也不会执行任何代码。这种方法显著提高了具有大量屏幕的应用程序的性能。
composable() 中的 key 参数允许管理屏幕的重新创建。默认情况下,composable 在重复导航到相同路由时不会重新创建——NavHost 使用现有的返回堆栈条目。但是,如果传递了 key 并且发生了变化,NavHost 会创建 composable 函数的新实例。这对于具有动态数据的屏幕非常有用,这些屏幕在重新打开时需要强制更新状态。
val NavGraphBuilder.Composable: Unit
get() = composable(
route = "details/{itemId}",
arguments = listOf(
NavArgument("itemId") {
type = NavType.IntType
}
),
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://details/{itemId}" }
)
) { backStackEntry ->
val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
DetailsScreen(itemId = itemId)
}
composable() 通过 arguments 参数支持灵活的参数系统。每个参数由 NavArgument 对象描述,该对象定义类型、默认值和必要性。参数在路由中作为路径参数(通过花括号)或查询参数(通过问号)传递。
路径参数直接在路由模板中指定:“profile/{userId}”。当导航到路由 “profile/42” 时,NavHost 会自动提取值 42 并通过 backStackEntry.arguments 使其可用。查询参数在问号后添加:“search?query={text}” 并由库自动解析。
在提取参数时,重要的是通过 NavType.isNullableAllowed 检查参数的必要性,并通过 NavArgument defaultValue 提供默认值。如果缺少必需参数,Navigation Compose 会生成 IllegalArgumentException 异常,从而防止因错误路由导致的不可见错误。
| 参数类型 | NavType | 路由示例 |
|---|---|---|
| Int | NavType.IntType | “item/{id}” |
| String | NavType.StringType | “user/{name}” |
| Boolean | NavType.BoolType | “filter?enabled={value}” |
| Float | NavType.FloatType | “map/{lat}/{lon}” |
| Long | NavType.LongType | “article/{timestamp}” |
对于传递复杂对象,建议使用 NavType.ParcelableType 或 NavType.SerializableType。然而,Google 建议最小化传递数据的大小——最好是传递标识符并在屏幕内按 ID 加载对象。这可以防止大型序列化数据带来的问题,并简化配置更改的处理。
data class Profile(val id: Int, val name: String) : Parcelable
// 以最少数据导航
navController.navigate("profile/42")
// 在屏幕上获取参数
composable(
route = "profile/{userId}",
arguments = listOf(
NavArgument("userId") { type = NavType.IntType }
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getInt("userId") ?: 0
ProfileDetailScreen(userId = userId)
}
在实际应用程序中,经常需要组织嵌套导航图——例如,在 BottomNavigation 的标签内创建单独的屏幕堆栈。composable() 通过嵌套 NavHost 机制支持嵌套:在 composable 屏幕内部可以声明自己的 NavHost 和独立的路由堆栈。
每个嵌套的 NavHost 都有自己的 NavController 和返回堆栈。这意味着标签内的导航不会影响其他标签中的导航——用户可以在标签之间自由切换,而不会丢失每个标签内的导航历史。这种架构称为 Scoped Navigation,Google 推荐将其用于具有复杂多层导航的应用程序。
在实现嵌套导航时,正确管理 NavController 的状态很重要:每个嵌套的 NavHost 应该在 composable 函数的作用域内存储自己的 rememberNavController。根据 Android Developer Summit 2024 的数据,超过 40% 的具有三个或更多标签的 Jetpack Compose 应用程序使用嵌套 NavHost 架构来隔离模块之间的导航。
// 带标签的主 NavHost
composable("tabs") {
MainTabsScreen { tab ->
when (tab) {
Tab.Home -> HomeNavGraph()
Tab.Search -> SearchNavGraph()
}
}
}
// Home 标签中的嵌套图
@Composable
fun HomeNavGraph() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home_feed"
) {
composable("home_feed") { FeedScreen() }
composable("home_detail/{postId}") { PostDetailScreen() }
}
}
在 Jetpack Compose 之前,Android 中的标准导航方法使用 Intent 和 FragmentManager。Intent — 是一个系统消息,用于启动新的 Activity,这意味着重新创建整个 View 树。与此不同,composable() 在单个 Activity 内工作,仅替换 Compose 树的一部分,这明显更快且内存效率更高。
composable() 与基于 Intent 的导航的主要区别:
| 特性 | composable() | Intent / Fragment |
|---|---|---|
| 架构 | 单 Activity,Compose 树 | 多 Activity,Fragment 堆栈 |
| 数据传输 | 路径/查询参数,共享 ViewModel | Intent extras,Bundle,SharedPreferences |
| 深层链接 | 内置 navDeepLink 支持 | manifest 中的 intent-filter |
| 返回堆栈 | 自动管理 popBackStack | FragmentManager.popBackStack() |
| 切换时间 | 5–15 毫秒(进程内) | 50–200 毫秒(重新创建) |
从 Intent 切换到 composable() — 不仅仅是 API 的更改,而是架构范式的转变。开发人员不再显式指定要打开哪个 Activity,而是在一个位置声明式地描述所有可能的路由,这提高了代码的可读性并简化了导航测试。根据 Google I/O 2024 的数据,Jetpack Compose 与 Navigation Compose 结合使用,可将导航代码量比 FragmentManager 减少 40-60%。
最常见的错误之一——在重组时重新创建 NavController。如果 NavController 通过 rememberNavController() 在父级 composable 级别创建,而父级 composable 可能在状态变化时重新创建,则导航会中断——导航历史会丢失。正确的解决方案是将 NavController 提升到稳定的 composable 级别,例如 Activity 级别或应用程序的根 composable 级别。
第二个常见问题——导航时的无限重组。当 navController.navigate() 调用直接放在 composable 函数体内时,就会发生这种情况。由于导航会更改 NavHost 的状态,这会触发重组,重组再次调用 navigate(),从而形成循环。所有导航调用都应包装在 lambda 处理程序(onClick、onButtonPressed)中,而不是在组合中执行。
第三个错误——使用 BottomNavigation 时返回堆栈管理不当。每次切换标签时通过 navigate() 进行的简单导航都会向堆栈添加新条目,而不是返回到现有条目。对于 BottomNavigation,应使用 navController.navigate() 并设置 restoreState = true 和 launchSingleTop = true,这可以确保在标签之间切换时正确恢复状态。
fun NavController.navigateToTab(route: String) {
navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
常见问题解答
composable() — 不是注解,而是 NavHost 的一个扩展函数,用于将路由绑定到 UI。普通的 @Composable 函数只描述布局,而 composable() 将此布局以指定路由注册到导航图中,使其可通过 NavController 进行导航。
建议仅通过路径参数传递 标识符(ID),并在屏幕上通过存储库或 ViewModel 按 ID 加载对象本身。如果仍需传递对象,请使用 NavType.ParcelableType,但避免传递超过 1 KB 的对象——这可能导致 TransactionTooLargeException。
屏幕旋转会导致 配置更改,默认情况下会重新创建 Activity。要保留 composable 屏幕的状态,对于简单数据使用 rememberSaveable,或使用具有相应屏幕作用域的 ViewModel。Navigation Compose 会在重新创建后恢复返回堆栈,但 composable() 函数内部的状态在没有 rememberSaveable 的情况下会被重置。
不可以,composable() — 是 NavGraphBuilder 的扩展函数,仅在 NavHost 块内可用。对于无需导航的简单 UI 部分替换,请使用条件渲染(when、if)或 AnimatedContent。composable() 正是为支持返回堆栈和深层链接的路由而设计的。
在 ViewModel 内使用 SavedStateHandle:首次导航时 handle.get(“initialized”) 将返回 null,返回时返回已保存的值。或者,通过 navController.previousBackStackEntry 分析返回堆栈中的当前位置——如果为 null,则这是导航堆栈中的第一个屏幕。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。