Navigation 组件与页面导航
问题
Jetpack Navigation 组件是如何简化 Android 页面导航的?与传统 Fragment 事务有什么区别?
答案
1. Navigation 组件架构
Navigation 三大核心组件:
| 组件 | 职责 |
|---|---|
| NavHost | 显示导航目的地的容器(通常是 NavHostFragment) |
| NavController | 控制导航操作(前进、返回、弹出栈等) |
| NavGraph | 定义所有目的地和导航路径的图 |
2. 基本使用
<!-- res/navigation/nav_graph.xml -->
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.HomeFragment"
android:label="首页">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment"
android:label="详情">
<argument
android:name="itemId"
app:argType="integer" />
</fragment>
</navigation>
<!-- Activity 布局 -->
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />
3. Safe Args(类型安全参数传递)
Safe Args 是一个 Gradle 插件,自动生成类型安全的导航代码:
// build.gradle.kts
plugins {
id("androidx.navigation.safeargs.kotlin")
}
// 发送方 - 自动生成 Directions 类
val action = HomeFragmentDirections.actionHomeToDetail(itemId = 42)
findNavController().navigate(action)
// 接收方 - 自动生成 Args 类
class DetailFragment : Fragment() {
private val args: DetailFragmentArgs by navArgs()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val itemId = args.itemId // 类型安全,无需 key 字符串
}
}
4. Deep Link
<!-- 导航图中声明 Deep Link -->
<fragment android:id="@+id/detailFragment">
<deepLink
android:id="@+id/deepLink"
app:uri="myapp://detail/{itemId}" />
</fragment>
<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
<nav-graph android:value="@navigation/nav_graph" />
</activity>
// 代码触发 Deep Link
val request = NavDeepLinkRequest.Builder
.fromUri("myapp://detail/42".toUri())
.build()
findNavController().navigate(request)
5. BottomNavigationView 集成
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = findNavController(R.id.nav_host_fragment)
// 一行代码绑定底部导航
findViewById<BottomNavigationView>(R.id.bottom_nav)
.setupWithNavController(navController)
}
}
6. Navigation vs 传统 Fragment 事务
| 特性 | Navigation 组件 | 传统 FragmentTransaction |
|---|---|---|
| 可视化 | 支持导航图编辑器 | 无可视化工具 |
| 参数传递 | Safe Args 类型安全 | Bundle 手动管理 |
| Deep Link | 内置支持 | 需手动实现 |
| 回退栈 | 自动管理 | 手动 addToBackStack |
| 动画 | 导航图中声明 | 代码或 XML 配置 |
| 底部导航 | setupWithNavController | 手动同步状态 |
常见面试问题
Q1: Navigation 组件如何处理回退栈?
答案:
Navigation 自动管理回退栈。每次 navigate() 会将目的地压栈,按返回键自动弹栈。也可通过 popUpTo 和 popUpToInclusive 控制弹栈行为:
<action
android:id="@+id/action_to_home"
app:destination="@id/homeFragment"
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="true" />
这会清除 homeFragment 之上(含自身)的所有页面,避免重复创建。
Q2: Navigation 组件的 Fragment 每次切换都会重建视图吗?如何优化?
答案:
默认每次导航到一个 Fragment 都会重建视图。优化方案:
- Navigation 2.4+ 支持多回退栈(
saveState/restoreState),配合 BottomNavigationView 可保持各 Tab 的回退栈和状态 - 使用 ViewModel 持有数据,视图重建后自动恢复
- 使用
SavedStateHandle保存轻量状态
Q3: Safe Args 和 Bundle 传参有什么区别?
答案:
- Safe Args:编译时类型检查,自动生成
Directions和Args类,IDE 有代码补全。缺点是需要额外 Gradle 插件 - Bundle:运行时通过 key 取值,容易拼错 key 或类型不匹配导致崩溃
- Safe Args 底层仍然使用 Bundle,只是封装了类型安全的访问层