7、Navigation:Navigation组件基础、Safe Args传参、深层链接与导航图

Navigation组件,说白了就是Google帮我们解决Fragment跳转混乱问题的官方方案。我记得刚入行那会儿,项目里Fragment的跳转全靠手写FragmentManager,各种add、replace、addToBackStack,代码写到最后自己都看不懂。后来有了Navigation,整个页面的流转变得清晰多了。

7.1 Navigation组件解决了什么问题

你想想看,一个稍微复杂点的App,少说也有十几个页面。如果用传统方式管理Fragment跳转,会出现什么问题?

  • FragmentManager的代码散落在各个Activity和Fragment里
  • 返回栈的管理全靠手动,稍不注意就栈溢出
  • 页面间传参没有统一规范,经常出现类型不匹配
  • 深层链接(Deep Link)实现起来特别麻烦

Navigation组件就是来解决这些痛点的。它把页面跳转抽象成一张导航图,所有的跳转逻辑都集中在一个XML文件里管理。我个人习惯把导航图看作是整个App的"交通地图",每个页面是一个目的地,页面之间的连线就是导航动作。

7.2 导航图的基本结构

导航图是一个XML文件,通常放在res/navigation/目录下。它的核心元素就两个:<destination><action>

<?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_graph"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.app.HomeFragment"
        android:label="首页"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detailFragment" />
    </fragment>

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

</navigation>

这里有个细节要注意:startDestination指定了入口页面,也就是App启动后第一个显示的Fragment。我在项目中遇到过一个问题,就是startDestination写错了,结果App一启动就崩溃,排查了半天才发现是导航图配置的问题。

7.3 Safe Args:类型安全的传参方式

页面跳转免不了要传参。传统做法是用Bundle,但Bundle有个问题——它是弱类型的。你传一个String,接收方用getInt去取,编译时不会报错,运行时才崩溃。

Safe Args就是为了解决这个问题而生的。它会根据导航图中的参数定义,自动生成对应的Direction类和Args类,编译时就能检查类型是否匹配。

7.3.1 定义参数

在导航图中,用<argument>标签来定义参数:

<fragment
    android:id="@+id/detailFragment"
    android:name="com.example.app.DetailFragment"
    android:label="详情">
    <argument
        android:name="itemId"
        app:argType="integer"
        android:defaultValue="0" />
    <argument
        android:name="itemName"
        app:argType="string"
        android:defaultValue="" />
</fragment>

支持的参数类型包括:integer、string、boolean、float、long、reference,以及自定义Parcelable类型。

7.3.2 使用Safe Args传参

配置好参数后,重新编译项目,Safe Args会自动生成代码。在发送方,可以这样使用:

val action = HomeFragmentDirections.actionHomeToDetail()
action.itemId = 1001
action.itemName = "Android进阶指南"
findNavController().navigate(action)

在接收方,通过args属性获取参数:

val args: DetailFragmentArgs by navArgs()
val itemId = args.itemId
val itemName = args.itemName

嗯,这里要注意:navArgs()是一个Kotlin属性委托,用起来非常方便。但如果你用的是Java,需要调用DetailFragmentArgs.fromBundle(getArguments())来获取参数。

小技巧:我个人习惯给每个参数都设置一个合理的默认值。这样即使传参失败,页面也不会直接崩溃,至少能显示一个空状态。

7.4 深层链接(Deep Link)

深层链接允许外部应用或网页直接跳转到App内部的某个页面。比如你收到一条短信,里面有个链接,点开直接跳到了App的商品详情页,这就是深层链接的典型应用。

7.4.1 在导航图中配置深层链接

<fragment
    android:id="@+id/detailFragment"
    android:name="com.example.app.DetailFragment"
    android:label="详情">
    <deepLink app:uri="example://detail/{itemId}" />
    <argument
        android:name="itemId"
        app:argType="integer" />
</fragment>

配置好后,当用户点击example://detail/1001这个链接时,系统会自动打开DetailFragment,并把itemId设置为1001。

7.4.2 处理深层链接的两种方式

第一种是隐式深层链接,不需要在Activity的IntentFilter里声明,Navigation组件会自动处理。第二种是显式深层链接,需要在AndroidManifest.xml中配置:

<activity
    android:name=".MainActivity"
    android:launchMode="singleTask">
    <nav-graph android:value="@navigation/nav_graph" />
</activity>

我曾经踩过一个坑:如果Activity的launchMode不是singleTask,深层链接可能会创建多个Activity实例,导致返回栈混乱。所以建议使用深层链接时,把launchMode设为singleTask。

7.5 导航图的最佳实践

在实际项目中,导航图可能会变得非常庞大。我建议按功能模块拆分导航图,然后用<include>标签组合起来:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/root_graph"
    app:startDestination="@id/mainFragment">

    <include app:graph="@navigation/home_graph" />
    <include app:graph="@navigation/profile_graph" />
    <include app:graph="@navigation/settings_graph" />

</navigation>

这样做的好处是:每个模块的导航逻辑独立,多人协作时不容易冲突。而且如果某个模块需要移除,直接删掉对应的导航图文件就行,不会影响其他模块。

注意事项:使用<include>时,被包含的导航图不能有重复的ID。我曾经因为两个模块都定义了相同ID的action,导致编译时直接报错。建议给每个模块的ID加上前缀,比如home_、profile_。

7.6 Navigation组件的核心流程图

Navigation组件核心工作流程 导航图定义 nav_graph.xml destination + action 参数定义 <argument> 标签 类型 + 默认值 深层链接配置 <deepLink> 标签 URI模板 编译时生成 Safe Args插件 生成Direction/Args类 类型检查 编译期类型校验 避免运行时崩溃 IntentFilter生成 自动注册深层链接 AndroidManifest合并 页面跳转 findNavController() navigate(action) 参数传递 Safe Args自动注入 navArgs() 获取 返回栈管理 自动入栈/出栈 popUpTo 控制 三个阶段:定义 → 编译 → 运行时,全程类型安全

7.7 避坑指南

最后分享几个我实际项目中踩过的坑:

  • 导航图ID冲突:多个模块使用相同ID的destination或action,编译时不会报错,但运行时跳转会跳到错误的页面。建议给ID加模块前缀。
  • Safe Args版本不匹配:Safe Args的版本必须和Navigation组件的版本一致,否则编译时会报错。我遇到过升级Navigation版本后忘记升级Safe Args插件的情况。
  • 深层链接的URI编码:如果URI中包含中文或特殊字符,记得进行URL编码。我曾经因为没编码,导致深层链接一直打不开。
  • 返回栈溢出:如果页面跳转循环引用(A→B→C→A),返回栈会无限增长。建议在跳转时使用popUpTo清理不必要的栈帧。

核心要点回顾:

  • 导航图是页面跳转的"交通地图",所有跳转逻辑集中管理
  • Safe Args提供类型安全的传参方式,编译期就能发现问题
  • 深层链接让外部可以直接跳转到App内部页面
  • 按模块拆分导航图,用<include>组合,便于维护

公众号:蓝海资料掘金营,微信deep3321