目录
  1. 1. 一、Navigation 设计哲学
    1. 1.1. 1.1 传统 Fragment 导航的痛点
    2. 1.2. 1.2 Navigation 三大核心概念
  2. 2. 二、NavGraph:声明式导航图
    1. 2.1. 2.1 XML 方式定义 NavGraph
    2. 2.2. 2.2 程序化 DSL 方式定义 NavGraph(Kotlin)
    3. 2.3. 2.3 嵌套导航图(Nested NavGraph)的实践
  3. 3. 三、NavHostFragment:导航容器
    1. 3.1. 3.1 在 Activity 中配置 NavHostFragment
    2. 3.2. 3.2 NavController 的获取方式
  4. 4. 四、Safe Args:类型安全的参数传递
    1. 4.1. 4.1 Safe Args 插件配置
    2. 4.2. 4.2 生成代码的使用
    3. 4.3. 4.3 支持的参数类型
  5. 5. 五、DeepLink 与外部导航
    1. 5.1. 5.1 隐式 DeepLink(通过 Intent Filter)
    2. 5.2. 5.2 显式 DeepLink(PendingIntent)
    3. 5.3. 5.3 手动处理 DeepLink
  6. 6. 六、NavigationUI:与 UI 组件集成
    1. 6.1. 6.1 BottomNavigationView 集成
    2. 6.2. 6.2 多返回栈支持(Multiple Back Stacks)
    3. 6.3. 6.3 DrawerLayout / NavigationView 集成
    4. 6.4. 6.4 AppBarConfiguration
  7. 7. 七、返回栈控制与高级导航
    1. 7.1. 7.1 NavOptions 详解
    2. 7.2. 7.2 常见导航模式
    3. 7.3. 7.3 使用 savedStateHandle 传递返回结果
  8. 8. 八、条件导航与登录流程
    1. 8.1. 8.1 全局登录拦截
  9. 9. 九、导航测试
    1. 9.1. 9.1 TestNavHostController
  10. 10. 十、Navigation 2.8+ 新特性
    1. 10.1. 10.1 类型安全路径(Type-Safe Routes)
  11. 11. 面试常考问题
JetPack全家桶(八)之Navigation导航组件

Navigation 是 Jetpack 中专门管理 Fragment 跳转与返回栈的组件。它通过声明式导航图(NavGraph)统一管理目标页面、跳转动画、参数传递,彻底告别手写 FragmentTransaction 的繁琐与易错。

一、Navigation 设计哲学

1.1 传统 Fragment 导航的痛点

在没有 Navigation 的年代,Android 中实现多页面导航需要:

// 传统方式 —— 每个跳转都需要写这么多代码
fun navigateToDetail(articleId: Int) {
val fragment = DetailFragment.newInstance(articleId)
parentFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack("detail_$articleId")
.setCustomAnimations(
R.anim.slide_in_right,
R.anim.slide_out_left,
R.anim.slide_in_left,
R.anim.slide_out_right
)
.commit()
}

核心痛点:

  1. 样板代码泛滥:每个页面跳转都需要手写 FragmentTransaction,动画参数、addToBackStack 容易遗漏
  2. 类型不安全:参数通过 Bundle 传递,运行时 ClassCastException 频发。调用方需要知道”参数名是 articleId 还是 article_id
  3. 返回栈管理复杂popBackStack 时开发者需要精确知道栈中有哪些 Fragment,容易造成栈状态异常
  4. 导航逻辑分散:跳转代码散落在 Activity、Fragment、Adapter 各处,难以梳理完整的导航链路
  5. DeepLink 难实现:外部跳转进应用需要手写 Intent Filter 和路径解析
  6. BottomNavigation 集成困难:切换 Tab 时是 replace 还是 show/hide?返回栈如何与 Tab 同步?

Navigation 组件通过声明式的方式解决了所有这些问题。

1.2 Navigation 三大核心概念

概念 说明
NavGraph 导航图:声明式定义所有目的地(Destination)和它们之间的导航路径(Action)
NavHost 导航容器:一个空壳 ViewGroup,根据导航指示替换展示不同的 Destination
NavController 导航控制器:执行导航操作(navigate、popBackStack 等)的核心对象
┌──────────────────────────────────────────────────────┐
│ Activity │
│ ┌──────────────────────────────────────────────┐ │
│ │ NavHostFragment (NavHost) │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ 当前显示的 Destination (Fragment) │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ │ NavController ← 管理返回栈和跳转 │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ [BottomNavigation / DrawerLayout / Toolbar] │
│ ↑ │
│ └── NavigationUI 自动同步 │
└──────────────────────────────────────────────────────┘

二、NavGraph:声明式导航图

2.1 XML 方式定义 NavGraph

res/navigation/nav_main.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/nav_main"
app:startDestination="@id/homeFragment">

<!-- ========== Fragment Destinations ========== -->

<fragment
android:id="@+id/homeFragment"
android:name="com.example.ui.home.HomeFragment"
android:label="首页"
tools:layout="@layout/fragment_home">

<!-- 到列表页的 Action -->
<action
android:id="@+id/action_home_to_article_list"
app:destination="@id/articleListFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />

<!-- 到设置页的 Action -->
<action
android:id="@+id/action_home_to_settings"
app:destination="@id/settingsFragment" />

<!-- 对于有参数的跳转,在 action 中声明 argument -->
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment">
<argument
android:name="articleId"
app:argType="integer" />
</action>

<!-- DeepLink 声明 -->
<deepLink
android:id="@+id/deepLinkFromNotification"
app:uri="myapp://home" />
</fragment>

<fragment
android:id="@+id/articleListFragment"
android:name="com.example.ui.list.ArticleListFragment"
android:label="文章列表"
tools:layout="@layout/fragment_article_list">

<action
android:id="@+id/action_list_to_detail"
app:destination="@id/detailFragment" />
</fragment>

<fragment
android:id="@+id/detailFragment"
android:name="com.example.ui.detail.DetailFragment"
android:label="详情页"
tools:layout="@layout/fragment_detail">

<!-- 声明目标页面接收的参数 -->
<argument
android:name="articleId"
app:argType="integer"
android:defaultValue="0" />

<argument
android:name="articleTitle"
app:argType="string"
android:defaultValue=""
app:nullable="true" />
</fragment>

<fragment
android:id="@+id/settingsFragment"
android:name="com.example.ui.settings.SettingsFragment"
android:label="设置" />

<!-- ========== Activity Destination(跨 Activity 导航) ========== -->
<activity
android:id="@+id/loginActivity"
android:name="com.example.ui.login.LoginActivity"
android:label="登录" />

<!-- ========== Dialog Destination ========== -->
<dialog
android:id="@+id/confirmDialog"
android:name="com.example.ui.common.ConfirmDialogFragment"
android:label="确认删除">
<argument
android:name="message"
app:argType="string" />
</dialog>

<!-- ========== 嵌套导航图(Nested NavGraph)========== -->
<navigation
android:id="@+id/onboarding_flow"
app:startDestination="@id/welcomeFragment">

<fragment
android:id="@+id/welcomeFragment"
android:name="com.example.onboarding.WelcomeFragment" />
<fragment
android:id="@+id/tosFragment"
android:name="com.example.onboarding.TosFragment" />
<fragment
android:id="@+id/signUpFragment"
android:name="com.example.onboarding.SignUpFragment" />
</navigation>

<!-- 全局 Action(任何页面都可以跳转到) -->
<action
android:id="@+id/action_global_login"
app:destination="@id/loginActivity" />

</navigation>

2.2 程序化 DSL 方式定义 NavGraph(Kotlin)

如果需要动态构建导航图,使用 Kotlin DSL:

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment

navHostFragment.navController.apply {
graph = createNavGraph()
}
}

private fun createNavGraph(): NavGraph {
return navController.createGraph(startDestination = "home") {

// Fragment 目的地
fragment<HomeFragment>("home") {
label = "首页"
}

fragment<DetailFragment>("detail/{articleId}") {
label = "详情"
// 声明参数
argument("articleId") {
type = NavType.IntType
defaultValue = 0
}
}

// Activity 目的地
activity<LoginActivity>("login")

// Navigation 目的地
navigation("onboarding", startDestination = "welcome") {
fragment<WelcomeFragment>("welcome")
fragment<TosFragment>("tos")
fragment<SignUpFragment>("sign_up")
}
}
}
}

2.3 嵌套导航图(Nested NavGraph)的实践

嵌套导航图用于将相关页面组织在一起,便于模块化和代码复用:

场景:引导流程(Onboarding)

onboarding_flow:
Welcome → ToS → SignUp → Complete
↑___________________________| (完成后弹出整个引导流程)

主流程:
Home → List → Detail
// 从主流程跳转到引导流程
findNavController().navigate(R.id.onboarding_flow)

// 引导流程完成后,一次性弹出整个引导流程栈
findNavController().navigate(
AppNavGraphDirections.actionGlobalHome(), // 全局 Action
NavOptions.Builder()
.setPopUpTo(R.id.onboarding_flow, inclusive = true) // 弹出嵌套图内的所有页面
.build()
)

三、NavHostFragment:导航容器

3.1 在 Activity 中配置 NavHostFragment

<!-- activity_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_main"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_nav" />

<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:menu="@menu/bottom_nav_menu"
app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

关键属性解释:

  • android:name="androidx.navigation.fragment.NavHostFragment":指定容器 Fragment 的实现类
  • app:navGraph="@navigation/nav_main":绑定导航图
  • app:defaultNavHost="true":拦截系统返回键,由 NavController 管理返回栈

3.2 NavController 的获取方式

// 在 Activity 中
val navController = findNavController(R.id.nav_host_fragment)

// 在 Fragment 中(必须在 NavHostFragment 内的 Fragment 中)
val navController = findNavController()

// 在 View 中(向上查找所处的 NavHostFragment)
val navController = view.findNavController()

// Compose 中
val navController = rememberNavController()

四、Safe Args:类型安全的参数传递

4.1 Safe Args 插件配置

// 根 build.gradle.kts
plugins {
id("androidx.navigation.safeargs.kotlin") version "2.7.7" apply false
}

// app/build.gradle.kts
plugins {
id("androidx.navigation.safeargs.kotlin")
}

4.2 生成代码的使用

Safe Args 为每个 Action 生成 *Directions 类,为每个有参数的 Destination 生成 *Args 类。

发送参数:

// 编译期生成 HomeFragmentDirections
val action = HomeFragmentDirections
.actionHomeToDetail(
articleId = 123,
articleTitle = "Kotlin 协程深入理解"
)
findNavController().navigate(action)

接收参数:

class DetailFragment : Fragment() {

// 使用 navArgs() 委托获取类型安全的参数
private val args: DetailFragmentArgs by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val articleId = args.articleId // Int 类型,无需手动 getInt()
val articleTitle = args.articleTitle // String 类型
// ...
}
}

4.3 支持的参数类型

Safe Args 支持以下类型:

NavType Kotlin/Java 类型 示例
NavType.IntType Integer argument android:name="count" app:argType="integer"
NavType.StringType String argument android:name="query" app:argType="string"
NavType.BoolType Boolean argument android:name="showHeader" app:argType="boolean"
NavType.FloatType Float argument android:name="rating" app:argType="float"
NavType.LongType Long argument android:name="timestamp" app:argType="long"
NavType.IntArrayType IntArray argument android:name="ids" app:argType="integer[]"
NavType.StringArrayType StringArray argument android:name="tags" app:argType="string[]"
NavType.ReferenceType @IdRes Int 资源引用 ID
自定义 Parcelable 实现 Parcelable 的类 argument android:name="user" app:argType="com.example.User"
自定义 Serializable 实现 Serializable 的类 argument android:name="config" app:argType="com.example.Config"
自定义 Enum Kotlin enum class argument android:name="type" app:argType="com.example.Type"

5.1 隐式 DeepLink(通过 Intent Filter)

在 NavGraph 中声明 deepLink:

<fragment
android:id="@+id/articleDetailFragment"
android:name="com.example.ArticleDetailFragment">
<argument android:name="articleId" app:argType="integer" />

<deepLink
app:uri="https://www.example.com/articles/{articleId}"
app:action="android.intent.action.VIEW" />

<deepLink
app:uri="myapp://article/{articleId}" />
</fragment>

当用户点击 https://www.example.com/articles/42 的链接时,Android 会自动导航到 articleDetailFragmentarticleId 参数为 42。

5.2 显式 DeepLink(PendingIntent)

// 构建一个指向特定页面的 PendingIntent
val pendingIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.nav_main)
.setDestination(R.id.articleDetailFragment)
.setArguments(Bundle().apply {
putInt("articleId", 42)
})
.createPendingIntent()

// 将这个 PendingIntent 用于通知
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("新文章推荐")
.setContentText("点击查看详情")
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build()
// 手动解析并执行 DeepLink
val deepLinkRequest = NavDeepLinkRequest.Builder
.fromUri("https://www.example.com/articles/42".toUri())
.build()

navController.navigate(deepLinkRequest)

六、NavigationUI:与 UI 组件集成

6.1 BottomNavigationView 集成

val navController = findNavController(R.id.nav_host_fragment)
val bottomNav = binding.bottomNavigationView

// 关键:setupWithNavController 自动处理 tab 选中与返回栈
bottomNav.setupWithNavController(navController)

menu item id 必须与 NavGraph 中 destination id 一致:

<!-- res/menu/bottom_nav_menu.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/homeFragment" <!-- 必须与 NavGraph destination id 一致 -->
android:icon="@drawable/ic_home"
android:title="首页" />
<item
android:id="@+id/articleListFragment"
android:icon="@drawable/ic_list"
android:title="文章" />
<item
android:id="@+id/settingsFragment"
android:icon="@drawable/ic_settings"
android:title="设置" />
</menu>

6.2 多返回栈支持(Multiple Back Stacks)

Navigation 2.4+ 支持多返回栈,每个 BottomNavigation tab 有独立的返回栈:

bottomNav.setupWithNavController(navController)

// 关键配置:保存和恢复每个 tab 的返回栈状态
// NavigationUI 内部使用 savedStateHandle 实现

当用户:

  1. 在 Tab A 深入到第三层页面
  2. 切换到 Tab B
  3. 再切换回 Tab A

Tab A 的返回栈状态不会有任何损失,之前所在的第三层页面仍然保留。

6.3 DrawerLayout / NavigationView 集成

val drawerLayout = binding.drawerLayout
val navView = binding.navigationView

// 方法 1:简单设置
NavigationUI.setupWithNavController(navView, navController)

// 方法 2:带 DrawerLayout 的设置
appBarConfiguration = AppBarConfiguration(
setOf(R.id.homeFragment, R.id.settingsFragment), // 顶层 destinations
drawerLayout
)
NavigationUI.setupWithNavController(
toolbar, navController, appBarConfiguration
)

// ActionBar 的汉堡图标和返回箭头自动切换
// 在顶层页面显示汉堡图标,在子页面显示返回箭头

6.4 AppBarConfiguration

// 定义哪些 destination 是"顶层"的(显示汉堡图标而非返回箭头)
val appBarConfiguration = AppBarConfiguration(
topLevelDestinationIds = setOf(
R.id.homeFragment,
R.id.articleListFragment,
R.id.settingsFragment
),
fallbackOnNavigateUpListener = ::onSupportNavigateUp // 可选:自定义返回行为
)

// 将这个配置用于 Toolbar + Navigation 联动
NavigationUI.setupActionBarWithNavController(
this, // AppCompatActivity
navController,
appBarConfiguration
)

七、返回栈控制与高级导航

7.1 NavOptions 详解

val navOptions = NavOptions.Builder()
// 动画
.setEnterAnim(R.anim.slide_in_right)
.setExitAnim(R.anim.slide_out_left)
.setPopEnterAnim(R.anim.slide_in_left)
.setPopExitAnim(R.anim.slide_out_right)

// 返回栈管理
.setPopUpTo(
destinationId = R.id.homeFragment,
inclusive = true, // true = 连同 homeFragment 一起弹出
saveState = false // true = 保存被弹出页面的状态
)

// 启动模式
.setLaunchSingleTop(true) // 如果目标已在栈顶,不创建新实例

// 恢复之前保存的状态
.setRestoreState(true)
.build()

findNavController().navigate(
R.id.detailFragment,
args,
navOptions
)

7.2 常见导航模式

// 模式 1:跳转到新页面,弹出中间页面(如:登录成功后进入主页)
findNavController().navigate(
R.id.homeFragment,
null,
NavOptions.Builder()
.setPopUpTo(R.id.nav_main, inclusive = true) // 清空整个栈
.build()
)

// 模式 2:返回上一页
findNavController().navigateUp()
// 或
findNavController().popBackStack()

// 模式 3:返回到指定页面
findNavController().popBackStack(R.id.homeFragment, inclusive = false)

// 模式 4:避免重复跳转同一页面
findNavController().navigate(
destinationId,
null,
NavOptions.Builder()
.setLaunchSingleTop(true)
.build()
)

7.3 使用 savedStateHandle 传递返回结果

类似 startActivityForResult,Navigation 2.3+ 支持在返回时将结果传回上一个 Destination:

// ===== 在 DetailFragment 中设置返回结果 =====
findNavController().previousBackStackEntry?.savedStateHandle?.apply {
set("result_key", "已阅读文章 42")
set("is_favorited", true)
}
findNavController().navigateUp()

// ===== 在 ListFragment 中接收返回结果 =====
findNavController().currentBackStackEntry?.savedStateHandle
?.getLiveData<String>("result_key")
?.observe(viewLifecycleOwner) { result ->
// 收到来自 DetailFragment 的返回结果
Toast.makeText(context, result, Toast.LENGTH_SHORT).show()
}

八、条件导航与登录流程

8.1 全局登录拦截

这是最常见的条件导航场景:用户想访问需要登录的页面,如果未登录则先跳转到登录页。

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val navController = findNavController(R.id.nav_host_fragment)

// 注册全局导航拦截器
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id in listOf(
R.id.profileFragment,
R.id.checkoutFragment,
R.id.orderHistoryFragment
)) {
val isLoggedIn = UserSession.isLoggedIn
if (!isLoggedIn) {
// 阻止导航,跳转到登录页
// 注意:在 addOnDestinationChangedListener 回调中 navigate 可能有风险
// 更好的做法是在执行导航前检查
}
}
}
}
}

更好的方式是在 ViewModel 中管理导航逻辑:

class ProfileViewModel : ViewModel() {
private val _navigationEvent = MutableLiveData<Event<NavDirections>>()
val navigationEvent: LiveData<Event<NavDirections>> = _navigationEvent

fun onProfileClicked() {
if (UserSession.isLoggedIn) {
_navigationEvent.value = Event(
HomeFragmentDirections.actionHomeToProfile()
)
} else {
_navigationEvent.value = Event(
HomeFragmentDirections.actionHomeToLogin()
)
}
}
}

// 使用 SingleLiveEvent / Event wrapper 避免配置变更时重复发射
class Event<out T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) null else {
hasBeenHandled = true
content
}
}
}

九、导航测试

9.1 TestNavHostController

@RunWith(AndroidJUnit4::class)
class NavigationTest {

@Test
fun testNavigateToDetail() {
// 创建测试用的 NavController
val navController = TestNavHostController(
ApplicationProvider.getApplicationContext()
)

// 创建一个 Fragment 并设置其 NavController
val scenario = launchFragmentInContainer<HomeFragment>(
themeResId = R.style.Theme_MyApp
)

scenario.onFragment { fragment ->
// 将测试 NavController 设置在 Fragment 的 view 上
navController.setGraph(R.navigation.nav_main)
Navigation.setViewNavController(fragment.requireView(), navController)
}

// 执行导航
navController.navigate(
HomeFragmentDirections.actionHomeToDetail(articleId = 1)
)

// 验证导航结果
assertThat(navController.currentDestination?.id)
.isEqualTo(R.id.detailFragment)
}
}

十、Navigation 2.8+ 新特性

10.1 类型安全路径(Type-Safe Routes)

Navigation 2.8.0+ 引入了基于 Kotlin 序列化的类型安全导航路由:

// 定义路由(Kotlin serializable data class)
@Serializable
data class ArticleDetail(val articleId: Int, val articleTitle: String = "")

@Serializable
object ArticleList

@Serializable
object Settings

// 导航
navController.navigate(ArticleDetail(articleId = 42, articleTitle = "Hello"))

// NavHost
NavHost(navController = navController, startDestination = ArticleList) {
composable<ArticleList> {
ArticleListScreen(onItemClick = { articleId ->
navController.navigate(ArticleDetail(articleId = articleId))
})
}
composable<ArticleDetail> { backStackEntry ->
val detail: ArticleDetail = backStackEntry.toRoute()
DetailScreen(articleId = detail.articleId)
}
}

这彻底替代了 Safe Args 插件,用 Kotlin 的编译时类型安全保证导航参数的正确性,无需 Gradle 插件。


面试常考问题

Q1:Navigation 与手动 FragmentTransaction 对比优势?

  1. 类型安全的 Safe Args 避免运行时类型转换错误
  2. 声明式导航图可视化,一目了然应用的整体导航结构
  3. 内置动画支持(进入/退出/弹出进入/弹出退出四种动画)
  4. 自动处理返回栈与 DeepLink
  5. 与 BottomNavigationView、DrawerLayout、Toolbar 无缝集成
  6. 支持多返回栈(每个 Tab 独立管理)
  7. savedStateHandle 传递返回结果代替 startActivityForResult
  8. 减少 FragmentManager 样板代码 80% 以上

Q2:NavController 的作用域与获取方式?

每个 NavHostFragment 有独立的 NavController — — 作用域是它所在的 Fragment/View 树。

  • Fragment.findNavController():查找当前 Fragment 所属的 NavController。该 Fragment 必须在 NavHostFragment 的后代 View 树内,否则抛出 IllegalStateException
  • View.findNavController():通过 View 树向上查找
  • Activity.findNavController(@IdRes viewId):通过指定 NavHostFragment 的 ID 查找

Q3:如何在 ViewModel 级别驱动导航?

推荐方式:

  1. ViewModel 通过 SingleLiveEventChannel<NavDirections> 暴露导航事件
  2. View 层(Fragment/Activity)观察事件并调用 NavController.navigate()
  3. 不要在 ViewModel 中持有 NavController 引用,这违反 ViewModel 应独立于 UI 框架的原则
  4. Navigation 2.8+ 的类型安全路由使这一模式更加自然

Q4:Navigation 如何处理配置变更?

Navigation 在配置变更时自动恢复状态:

  • Fragment 的 savedInstanceStateFragmentManager 管理,Navigation 只是创建和替换 Fragment
  • NavController 本身的状态(返回栈、当前 destination)由 NavHostFragment 保存
  • savedStateHandle 的数据在配置变更和进程重建时都存活
  • 导航参数通过 Bundle 保存,配置变更时自动恢复
  • 使用 navArgs() 委托的参数值在配置变更后依然有效

Q5:Safe Args 生成的代码长什么样?

Safe Args 生成的代码本质上是普通的 Fragment 导航代码。它生成了:

  • *Directions 类:包含 action*() 方法,内部创建 NavDirections(包含 destinationId、Bundle 参数、NavOptions)
  • *Args 类:提供 fromBundle(Bundle) 静态方法,委托给 navArgs() 使用的 NavArgsLazy

这使得代码完全类型安全 —— 如果参数类型不匹配,编译就会失败,而非运行时崩溃。

打赏
  • 微信
  • 支付宝

评论