composable():什么是它,NavHost 与 Jetpack Compose 中的路由

作者: IT Sectr 发布日期: 2026-06-30 阅读时间: 9 分钟

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() — 是 Navigation Compose 库中在 NavHost 注册屏幕的函数。
  • 路由 — 每个屏幕通过一个作为第一个参数传递的字符串路由来标识。
  • 参数 — composable() 通过 NavArgument 支持参数,包括必需和可选参数。
  • 嵌套 — 支持通过具有独立路由图的嵌套 NavHost 进行嵌套导航。
  • 性能 — composable() 使用懒加载:屏幕仅在首次导航时创建。

NavHost 中的 composable() 是什么

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,从而可以组织到其他屏幕的导航。这种架构使导航变得明确且可预测。

kotlin
@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() 如何工作:键和参数

每次 composable() 调用都会在 NavHost 的内部图中创建一个具有唯一路由标识符的节点。当 NavController 执行 navigate() 时,库会将请求的路由与所有已注册的 composable 节点进行比较并找到匹配项。匹配后,会创建一个 NavBackStackEntry,将其放置在导航堆栈上,并启动 UI 组合。

composable() 的内部实现使用 懒加载 机制:屏幕组合仅在首次导航到此路由时发生。这意味着用户从未导航到的屏幕不会占用内存,也不会执行任何代码。这种方法显著提高了具有大量屏幕的应用程序的性能。

composable() 中的 key 参数允许管理屏幕的重新创建。默认情况下,composable 在重复导航到相同路由时不会重新创建——NavHost 使用现有的返回堆栈条目。但是,如果传递了 key 并且发生了变化,NavHost 会创建 composable 函数的新实例。这对于具有动态数据的屏幕非常有用,这些屏幕在重新打开时需要强制更新状态。

kotlin
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() 传递参数

composable() 通过 arguments 参数支持灵活的参数系统。每个参数由 NavArgument 对象描述,该对象定义类型、默认值和必要性。参数在路由中作为路径参数(通过花括号)或查询参数(通过问号)传递。

路径参数直接在路由模板中指定:“profile/{userId}”。当导航到路由 “profile/42” 时,NavHost 会自动提取值 42 并通过 backStackEntry.arguments 使其可用。查询参数在问号后添加:“search?query={text}” 并由库自动解析。

在提取参数时,重要的是通过 NavType.isNullableAllowed 检查参数的必要性,并通过 NavArgument defaultValue 提供默认值。如果缺少必需参数,Navigation Compose 会生成 IllegalArgumentException 异常,从而防止因错误路由导致的不可见错误。

参数类型NavType路由示例
IntNavType.IntType“item/{id}”
StringNavType.StringType“user/{name}”
BooleanNavType.BoolType“filter?enabled={value}”
FloatNavType.FloatType“map/{lat}/{lon}”
LongNavType.LongType“article/{timestamp}”

对于传递复杂对象,建议使用 NavType.ParcelableTypeNavType.SerializableType。然而,Google 建议最小化传递数据的大小——最好是传递标识符并在屏幕内按 ID 加载对象。这可以防止大型序列化数据带来的问题,并简化配置更改的处理。

kotlin
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)
}

使用 composable() 进行嵌套导航

在实际应用程序中,经常需要组织嵌套导航图——例如,在 BottomNavigation 的标签内创建单独的屏幕堆栈。composable() 通过嵌套 NavHost 机制支持嵌套:在 composable 屏幕内部可以声明自己的 NavHost 和独立的路由堆栈。

每个嵌套的 NavHost 都有自己的 NavController 和返回堆栈。这意味着标签内的导航不会影响其他标签中的导航——用户可以在标签之间自由切换,而不会丢失每个标签内的导航历史。这种架构称为 Scoped Navigation,Google 推荐将其用于具有复杂多层导航的应用程序。

在实现嵌套导航时,正确管理 NavController 的状态很重要:每个嵌套的 NavHost 应该在 composable 函数的作用域内存储自己的 rememberNavController。根据 Android Developer Summit 2024 的数据,超过 40% 的具有三个或更多标签的 Jetpack Compose 应用程序使用嵌套 NavHost 架构来隔离模块之间的导航。

kotlin
// 带标签的主 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() }
    }
}

composable() 与 Intent 导航的比较

在 Jetpack Compose 之前,Android 中的标准导航方法使用 Intent 和 FragmentManager。Intent — 是一个系统消息,用于启动新的 Activity,这意味着重新创建整个 View 树。与此不同,composable() 在单个 Activity 内工作,仅替换 Compose 树的一部分,这明显更快且内存效率更高。

composable() 与基于 Intent 的导航的主要区别:

  • 速度 — composable() 在毫秒内切换屏幕而无需重新创建 Activity,Intent 需要重新启动 Activity。
  • 动画 — 在 Navigation Compose 中,过渡动画通过 AnimatedNavHost 声明式设置,无需 overridePendingTransition。
  • 共享状态 — composable() 在共享的 ViewModel 作用域内工作,简化了屏幕之间的数据传输,无需 Intent extras。
特性composable()Intent / Fragment
架构单 Activity,Compose 树多 Activity,Fragment 堆栈
数据传输路径/查询参数,共享 ViewModelIntent extras,Bundle,SharedPreferences
深层链接内置 navDeepLink 支持manifest 中的 intent-filter
返回堆栈自动管理 popBackStackFragmentManager.popBackStack()
切换时间5–15 毫秒(进程内)50–200 毫秒(重新创建)

从 Intent 切换到 composable() — 不仅仅是 API 的更改,而是架构范式的转变。开发人员不再显式指定要打开哪个 Activity,而是在一个位置声明式地描述所有可能的路由,这提高了代码的可读性并简化了导航测试。根据 Google I/O 2024 的数据,Jetpack Compose 与 Navigation Compose 结合使用,可将导航代码量比 FragmentManager 减少 40-60%。

composable() 的常见错误

最常见的错误之一——在重组时重新创建 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,这可以确保在标签之间切换时正确恢复状态。

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

常见问题解答

composable() 和普通 @Composable 函数有什么区别?

composable() — 不是注解,而是 NavHost 的一个扩展函数,用于将路由绑定到 UI。普通的 @Composable 函数只描述布局,而 composable() 将此布局以指定路由注册到导航图中,使其可通过 NavController 进行导航。

如何在 composable() 屏幕之间传递复杂对象?

建议仅通过路径参数传递 标识符(ID),并在屏幕上通过存储库或 ViewModel 按 ID 加载对象本身。如果仍需传递对象,请使用 NavType.ParcelableType,但避免传递超过 1 KB 的对象——这可能导致 TransactionTooLargeException。

为什么 composable() 屏幕在旋转时重新创建?

屏幕旋转会导致 配置更改,默认情况下会重新创建 Activity。要保留 composable 屏幕的状态,对于简单数据使用 rememberSaveable,或使用具有相应屏幕作用域的 ViewModel。Navigation Compose 会在重新创建后恢复返回堆栈,但 composable() 函数内部的状态在没有 rememberSaveable 的情况下会被重置。

可以在没有 NavHost 的情况下使用 composable() 吗?

不可以,composable() — 是 NavGraphBuilder 的扩展函数,仅在 NavHost 块内可用。对于无需导航的简单 UI 部分替换,请使用条件渲染(when、if)或 AnimatedContent。composable() 正是为支持返回堆栈和深层链接的路由而设计的。

如何区分 composable() 中的首次导航和返回?

在 ViewModel 内使用 SavedStateHandle:首次导航时 handle.get(“initialized”) 将返回 null,返回时返回已保存的值。或者,通过 navController.previousBackStackEntry 分析返回堆栈中的当前位置——如果为 null,则这是导航堆栈中的第一个屏幕。

总结

  • composable() — 在 NavHost 中注册屏幕的函数,是在 Jetpack Compose 中组织导航的主要方式。
  • 路由 — 每个屏幕由一个带有可选路径和查询参数的路由字符串标识。
  • 参数 — 通过 NavArgument 传递,支持原始类型、Parcelable 和 Serializable 类型。
  • 嵌套 — composable() 支持嵌套 NavHost,用于组织具有独立堆栈的模块化导航。
  • 性能 — 屏幕的懒加载节省内存,屏幕切换时间为 5-15 毫秒。
  • 错误 — 主要问题:重新创建 NavController、在 composable 体内调用 navigate() 时无限重组、BottomNavigation 操作不正确。
  • 迁移 — 从 FragmentManager 迁移到 composable() 可将导航代码量减少 40-60%,并消除与 Fragment 生命周期相关的一类错误。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读