本章深入 Android 现代导航体系:Navigation Component 原理、Compose Navigation 类型安全路由、Deep Link 与 App Link 配置、Fragment 返回栈管理,以及多模块导航架构。
📋 章节目录
| 节 | 主题 |
|---|---|
| 4.1 | Navigation Component 原理与 NavGraph |
| 4.2 | Fragment 导航与返回栈管理 |
| 4.3 | Compose Navigation(NavHost / NavController) |
| 4.4 | 类型安全路由(Kotlin Serialization) |
| 4.5 | Deep Link 与 App Link |
| 4.6 | 多模块导航与嵌套导航图 |
4.1 Navigation Component 原理与 NavGraph
核心架构
NavController(导航控制器)
│
├── NavGraph(导航图:目的地 + 路径的集合)
│ ├── NavDestination(目的地:Fragment / Activity / Dialog)
│ └── NavAction(动作:目的地 + 参数 + 动画)
│
└── NavBackStack(返回栈:记录导航历史)
NavHost(容器:承载目的地 UI 的 Fragment/Composable)
kotlin
// build.gradle.kts
dependencies {
val navVersion = "2.8.0"
implementation("androidx.navigation:navigation-fragment-ktx:$navVersion")
implementation("androidx.navigation:navigation-ui-ktx:$navVersion")
implementation("androidx.navigation:navigation-compose:$navVersion")
}
NavGraph XML 定义
xml
<!-- res/navigation/main_nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_nav_graph"
app:startDestination="@id/homeFragment">
<!-- 主页 -->
<fragment
android:id="@+id/homeFragment"
android:name="com.example.ui.home.HomeFragment"
android:label="首页"
tools:layout="@layout/fragment_home">
<!-- 跳转到产品详情 -->
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/productDetailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
<!-- Deep Link -->
<deepLink
android:id="@+id/deepLinkHome"
app:uri="https://www.example.com/home" />
</fragment>
<!-- 产品详情 -->
<fragment
android:id="@+id/productDetailFragment"
android:name="com.example.ui.detail.ProductDetailFragment"
android:label="产品详情"
tools:layout="@layout/fragment_product_detail">
<!-- 参数定义 -->
<argument
android:name="productId"
app:argType="integer"
android:defaultValue="-1" />
<argument
android:name="productName"
app:argType="string"
android:defaultValue="" />
<deepLink
android:id="@+id/deepLinkDetail"
app:uri="https://www.example.com/product/{productId}" />
</fragment>
<!-- 购物车(弹出对话框)-->
<dialog
android:id="@+id/cartBottomSheet"
android:name="com.example.ui.cart.CartBottomSheetFragment" />
<!-- 嵌套 NavGraph(登录子图)-->
<navigation
android:id="@+id/authGraph"
android:label="认证流程"
app:startDestination="@id/loginFragment">
<fragment android:id="@+id/loginFragment"
android:name="com.example.ui.auth.LoginFragment" />
<fragment android:id="@+id/registerFragment"
android:name="com.example.ui.auth.RegisterFragment" />
</navigation>
</navigation>
4.2 Fragment 导航与返回栈管理
kotlin
// MainActivity.kt - 设置 NavController
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 获取 NavController
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
// 设置 Toolbar(自动处理标题和返回按钮)
val appBarConfig = AppBarConfiguration(
// 顶级目的地(不显示返回按钮)
topLevelDestinations = setOf(
R.id.homeFragment,
R.id.searchFragment,
R.id.profileFragment
)
)
setupActionBarWithNavController(navController, appBarConfig)
// 绑定底部导航
binding.bottomNavigation.setupWithNavController(navController)
// 监听导航变化
navController.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.homeFragment, R.id.searchFragment -> {
binding.bottomNavigation.isVisible = true
}
else -> {
binding.bottomNavigation.isVisible = false
}
}
}
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
// HomeFragment.kt - 触发导航
class HomeFragment : Fragment(R.layout.fragment_home) {
// Safe Args 生成的方向类(推荐,类型安全)
// kapt("androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion")
private val navController by lazy { findNavController() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.btnGoToDetail.setOnClickListener {
// ✅ 使用 Safe Args(类型安全)
val action = HomeFragmentDirections.actionHomeToDetail(
productId = 1001,
productName = "Android 深度指南"
)
navController.navigate(action)
}
// 带 NavOptions 的导航(控制返回栈)
binding.btnGoToHome.setOnClickListener {
navController.navigate(
R.id.homeFragment,
null,
NavOptions.Builder()
.setPopUpTo(R.id.homeFragment, inclusive = false) // 清空至主页
.setLaunchSingleTop(true) // 防止重复创建
.build()
)
}
}
private lateinit var binding: FragmentHomeBinding
}
// ProductDetailFragment.kt - 接收参数
class ProductDetailFragment : Fragment(R.layout.fragment_product_detail) {
// Safe Args 自动生成
private val args: ProductDetailFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val productId = args.productId
val productName = args.productName
binding.tvTitle.text = productName
loadProduct(productId)
}
// 返回结果给上一个页面(Fragment Result API)
private fun onAddToCartSuccess() {
setFragmentResult("add_to_cart", bundleOf("product_id" to args.productId))
findNavController().popBackStack()
}
private fun loadProduct(id: Int) { /* ... */ }
private lateinit var binding: FragmentProductDetailBinding
}
// HomeFragment 接收返回结果
class HomeFragmentReceiver : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// ✅ Fragment Result API(替代 startActivityForResult)
setFragmentResultListener("add_to_cart") { _, bundle ->
val productId = bundle.getInt("product_id")
showToast("商品 $productId 已加入购物车")
}
}
private fun showToast(message: String) { /* ... */ }
}
4.3 Compose Navigation
基础配置
kotlin
// 路由常量定义
object Routes {
const val HOME = "home"
const val PRODUCT_LIST = "product_list"
const val PRODUCT_DETAIL = "product_detail/{productId}"
const val PROFILE = "profile"
const val CART = "cart"
const val SEARCH = "search?query={query}"
fun productDetail(productId: Int) = "product_detail/$productId"
fun search(query: String = "") = "search?query=$query"
}
// 主 NavHost 配置
@Composable
fun AppNavHost(
navController: NavHostController = rememberNavController(),
startDestination: String = Routes.HOME
) {
NavHost(
navController = navController,
startDestination = startDestination,
// 全局过渡动画
enterTransition = {
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Left, tween(300))
},
exitTransition = {
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, tween(300))
},
popEnterTransition = {
slideIntoContainer(AnimatedContentTransitionScope.SlideDirection.Right, tween(300))
},
popExitTransition = {
slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Right, tween(300))
}
) {
composable(Routes.HOME) {
HomeScreen(
onProductClick = { productId ->
navController.navigate(Routes.productDetail(productId))
},
onSearchClick = {
navController.navigate(Routes.search())
}
)
}
composable(
route = Routes.PRODUCT_DETAIL,
arguments = listOf(
navArgument("productId") {
type = NavType.IntType
defaultValue = -1
}
),
deepLinks = listOf(
navDeepLink {
uriPattern = "https://www.example.com/product/{productId}"
}
)
) { backStackEntry ->
val productId = backStackEntry.arguments?.getInt("productId") ?: -1
ProductDetailScreen(
productId = productId,
onNavigateUp = { navController.popBackStack() },
onCartClick = { navController.navigate(Routes.CART) }
)
}
composable(
route = Routes.SEARCH,
arguments = listOf(
navArgument("query") {
type = NavType.StringType
defaultValue = ""
nullable = true
}
)
) { backStackEntry ->
val query = backStackEntry.arguments?.getString("query") ?: ""
SearchScreen(initialQuery = query)
}
// 嵌套导航图(认证流程)
navigation(
route = "auth",
startDestination = "login"
) {
composable("login") {
LoginScreen(
onLoginSuccess = {
navController.navigate(Routes.HOME) {
popUpTo("auth") { inclusive = true }
}
},
onRegisterClick = {
navController.navigate("register")
}
)
}
composable("register") {
RegisterScreen(
onRegisterSuccess = {
navController.popBackStack("login", inclusive = false)
}
)
}
}
}
}
// 带底部导航的 Scaffold
@Composable
fun MainScaffold() {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val bottomNavItems = listOf(
BottomNavItem(Routes.HOME, Icons.Default.Home, "首页"),
BottomNavItem(Routes.PRODUCT_LIST, Icons.Default.ShoppingBag, "购物"),
BottomNavItem(Routes.CART, Icons.Default.ShoppingCart, "购物车"),
BottomNavItem(Routes.PROFILE, Icons.Default.Person, "我的")
)
// 只在顶级页面显示底部导航
val showBottomNav = bottomNavItems.any { it.route == currentRoute }
Scaffold(
bottomBar = {
AnimatedVisibility(visible = showBottomNav) {
NavigationBar {
bottomNavItems.forEach { item ->
NavigationBarItem(
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
// 避免多次压栈
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) }
)
}
}
}
}
) { padding ->
AppNavHost(
navController = navController,
modifier = Modifier.padding(padding)
)
}
}
data class BottomNavItem(val route: String, val icon: ImageVector, val label: String)
传递返回结果(Compose)
kotlin
// 子页面设置结果
@Composable
fun AddressPickerScreen(navController: NavController) {
val addresses = listOf("北京市朝阳区", "上海市浦东新区")
LazyColumn {
items(addresses) { address ->
ListItem(
headlineContent = { Text(address) },
modifier = Modifier.clickable {
// ✅ 通过 previousBackStackEntry 传递结果
navController.previousBackStackEntry
?.savedStateHandle
?.set("selected_address", address)
navController.popBackStack()
}
)
}
}
}
// 父页面接收结果
@Composable
fun CheckoutScreen(navController: NavController) {
// ✅ 从 currentBackStackEntry.savedStateHandle 接收
val selectedAddress by navController.currentBackStackEntry
?.savedStateHandle
?.getStateFlow("selected_address", "")
?.collectAsStateWithLifecycle()
?: remember { mutableStateOf("") }
Column {
Text("收货地址: $selectedAddress")
Button(onClick = { navController.navigate("address_picker") }) {
Text("选择地址")
}
}
}
4.4 类型安全路由(Kotlin Serialization)
Navigation 2.8.0+ 支持基于 Kotlin Serialization 的类型安全路由,彻底告别字符串路由。
kotlin
// build.gradle.kts
plugins {
kotlin("plugin.serialization") version "2.0.0"
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
implementation("androidx.navigation:navigation-compose:2.8.0")
}
// 定义类型安全路由(无需字符串)
@Serializable
object HomeRoute
@Serializable
object CartRoute
@Serializable
data class ProductDetailRoute(val productId: Int, val productName: String)
@Serializable
data class SearchRoute(val query: String = "")
// NavHost 使用类型安全路由
@Composable
fun TypeSafeNavHost() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = HomeRoute) {
composable<HomeRoute> {
HomeScreen(
onProductClick = { id, name ->
navController.navigate(ProductDetailRoute(id, name))
}
)
}
composable<ProductDetailRoute> { backStackEntry ->
// ✅ 直接获取类型安全的路由参数(无需手动解析)
val route = backStackEntry.toRoute<ProductDetailRoute>()
ProductDetailScreen(
productId = route.productId,
productName = route.productName,
onNavigateUp = { navController.popBackStack() }
)
}
composable<SearchRoute> { backStackEntry ->
val route = backStackEntry.toRoute<SearchRoute>()
SearchScreen(initialQuery = route.query)
}
composable<CartRoute> {
CartScreen(onNavigateUp = { navController.popBackStack() })
}
}
}
4.5 Deep Link 与 App Link
配置 App Links(HTTP Deep Link)
xml
<!-- AndroidManifest.xml -->
<activity
android:name=".ui.main.MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="www.example.com"
android:pathPrefix="/product/" />
</intent-filter>
<!-- 自定义 Scheme(不需要 autoVerify)-->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="product" />
</intent-filter>
</activity>
kotlin
// 处理 Deep Link(Compose Navigation)
@Composable
fun AppNavHost() {
val navController = rememberNavController()
NavHost(navController, startDestination = HomeRoute) {
composable<ProductDetailRoute>(
deepLinks = listOf(
navDeepLink { uriPattern = "https://www.example.com/product/{productId}" },
navDeepLink { uriPattern = "myapp://product?id={productId}" }
)
) { backStackEntry ->
val route = backStackEntry.toRoute<ProductDetailRoute>()
ProductDetailScreen(productId = route.productId, productName = route.productName, onNavigateUp = {})
}
}
}
// MainActivity 处理 Deep Link
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
// ✅ handleDeepLink 自动将 intent 中的 URI 映射到对应页面
LaunchedEffect(Unit) {
intent?.let { navController.handleDeepLink(it) }
}
AppNavHost(navController = navController)
}
}
// 处理已运行时的 Deep Link(如推送通知)
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// navController.handleDeepLink(intent) // 在 Composable 中通过 LaunchedEffect 处理
}
}
生成 assetlinks.json(验证 App Link)
json
// 需要托管到:https://www.example.com/.well-known/assetlinks.json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:..."
]
}
}
]
bash
# 获取签名 SHA-256(用于 assetlinks.json)
keytool -list -v -keystore your-release-key.jks -alias your-alias
4.6 多模块导航与嵌套导航图
多模块导航架构
:app
└── AppNavHost(组合所有模块路由)
:feature:home
└── homeNavGraph(extension function)
:feature:product
└── productNavGraph(extension function)
:feature:auth
└── authNavGraph(extension function)
:core:navigation(共享路由定义)
└── Routes / Destination(各模块共享)
kotlin
// :core:navigation 模块
// Destinations.kt(所有模块共享)
@Serializable object HomeDestination
@Serializable data class ProductDetailDestination(val productId: Int)
@Serializable object CartDestination
@Serializable object LoginDestination
// NavigationActions 接口(各模块实现)
interface HomeNavigationActions {
fun navigateToProductDetail(productId: Int)
fun navigateToCart()
fun navigateToSearch()
}
// :feature:home 模块
// homeNavGraph.kt
fun NavGraphBuilder.homeNavGraph(
navController: NavController
) {
composable<HomeDestination> {
HomeScreen(
onProductClick = { productId ->
navController.navigate(ProductDetailDestination(productId))
},
onCartClick = {
navController.navigate(CartDestination)
}
)
}
}
// :feature:product 模块
fun NavGraphBuilder.productNavGraph(
navController: NavController
) {
composable<ProductDetailDestination> { backStackEntry ->
val dest = backStackEntry.toRoute<ProductDetailDestination>()
ProductDetailScreen(
productId = dest.productId,
onNavigateUp = { navController.popBackStack() }
)
}
}
// :app 模块
// AppNavHost.kt
@Composable
fun AppNavHost(navController: NavHostController) {
NavHost(navController, startDestination = HomeDestination) {
homeNavGraph(navController)
productNavGraph(navController)
authNavGraph(navController)
cartNavGraph(navController)
}
}
Demo 代码:chapter04
kotlin
// chapter04/NavigationDemo.kt
package com.example.androiddemos.chapter04
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.*
import kotlinx.serialization.Serializable
// 类型安全路由
@Serializable object DemoHome
@Serializable data class DemoDetail(val id: Int, val title: String)
@Serializable object DemoCart
@Composable
fun Chapter04NavigationDemo() {
val navController = rememberNavController()
val currentEntry by navController.currentBackStackEntryAsState()
val showBottomBar = currentEntry?.destination?.route in listOf(
DemoHome::class.qualifiedName,
DemoCart::class.qualifiedName
)
Scaffold(
bottomBar = {
if (showBottomBar) {
NavigationBar {
NavigationBarItem(
selected = currentEntry?.destination?.route == DemoHome::class.qualifiedName,
onClick = {
navController.navigate(DemoHome) {
launchSingleTop = true
restoreState = true
}
},
icon = { Icon(Icons.Default.Home, null) },
label = { Text("首页") }
)
NavigationBarItem(
selected = currentEntry?.destination?.route == DemoCart::class.qualifiedName,
onClick = {
navController.navigate(DemoCart) {
launchSingleTop = true
restoreState = true
}
},
icon = { Icon(Icons.Default.ShoppingCart, null) },
label = { Text("购物车") }
)
}
}
}
) { padding ->
NavHost(
navController = navController,
startDestination = DemoHome,
modifier = Modifier.padding(padding)
) {
composable<DemoHome> {
DemoHomeScreen(
onItemClick = { id ->
navController.navigate(DemoDetail(id, "产品 #$id"))
}
)
}
composable<DemoDetail> { entry ->
val route = entry.toRoute<DemoDetail>()
DemoDetailScreen(
detail = route,
onBack = { navController.popBackStack() }
)
}
composable<DemoCart> {
DemoCartScreen()
}
}
}
}
@Composable
private fun DemoHomeScreen(onItemClick: (Int) -> Unit) {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text("导航 Demo - 首页", style = MaterialTheme.typography.headlineSmall)
Text("点击产品跳转到详情页(类型安全路由)", style = MaterialTheme.typography.bodyMedium)
Spacer(Modifier.height(8.dp))
repeat(5) { index ->
Card(
modifier = Modifier.fillMaxWidth(),
onClick = { onItemClick(index + 1) }
) {
Row(
modifier = Modifier.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("产品 #${index + 1}")
Icon(Icons.Default.ChevronRight, contentDescription = null)
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DemoDetailScreen(detail: DemoDetail, onBack: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(detail.title) },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.Default.ArrowBack, contentDescription = "返回")
}
}
)
}
) { padding ->
Column(
modifier = Modifier.padding(padding).padding(16.dp).fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("产品 ID: ${detail.id}", style = MaterialTheme.typography.headlineMedium)
Spacer(Modifier.height(8.dp))
Text(detail.title, style = MaterialTheme.typography.bodyLarge)
Spacer(Modifier.height(24.dp))
Button(onClick = onBack) { Text("返回首页") }
}
}
}
@Composable
private fun DemoCartScreen() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("购物车页面", style = MaterialTheme.typography.headlineSmall)
}
}
章节总结
| 知识点 | 必掌握程度 | 面试频率 |
|---|---|---|
| Navigation Component 核心概念 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Fragment 返回栈管理 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Safe Args 类型安全参数 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Compose Navigation 基础 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 类型安全路由(Serialization) | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Deep Link / App Link | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Fragment Result API | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 多模块导航架构 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
👉 下一章:第五章------网络与数据持久化