Android Navigation 架构

Navigation 框架

关系图

  • NavHostFragment 是 Android Navigation 组件的页面容器宿主 Fragment,所有 导航图 里定义的 Fragment 都会挂载、渲染在它内部。 可以在 app:navGraph 属性指定路由配置文件,包含了所有的页面信息。
    并且NavHostFragment 对象内部 持有了 NavController,所有的页面跳转逻辑都是通过它来分发
  • NavController 持有了 NavGraph 以及 NavigatorProvider
  • NavGraph 维护了 对应于nav_graph.xml 文件解析得来的节点信息, 内部使用了SparseArray(优化的双列集合)缓存了解析的结果destination, 可以通过 资源 id 来查找 保留了节点信息的 destination 对象。同时记录了 启动页的 资源id。
  • NavigatorProvider 则维护了实际的跳转对象 即Navigator,例如ActivityNavigator 和 FragmentNavigator。
    最终执行的 NavController.navigate() 会根据 destination 的类型或者说创建destination的navigator的类型,例如Activity、Fragment 得到对应的 navigator 来执行对应的 navigate() 逻辑,最终底层就是 fragment的replace 方法 和 activity 的 startActivity 方法

nav_graph.xml 如何生成路由表

从NavHostFragement→NavGraph

FragmentDestinationNavGraphFragmentNavigatorNavGraphNavigatorNavigatorProviderNavInflaterNavControllerNavHostFragmentFragmentDestinationNavGraphFragmentNavigatorNavGraphNavigatorNavigatorProviderNavInflaterNavControllerNavHostFragmentloop[遍历每个子节点 (深度优先)]onCreate() / setGraph()inflate(R.navigation.nav_graph)getNavigator("navigation")NavGraphNavigatorcreateDestination()new NavGraph()onInflate() ->> 解析 startDestinationgetNavigator("fragment")FragmentNavigatorcreateDestination()new FragmentDestination()onInflate() ->> 解析 id, name 等addDestination(FragmentDestination)返回完整的 NavGraph设置为当前导航图

追问:NavGraph 内部是如何存储这些节点的?
NavGraph 内部使用了一个 SparseArrayCompat<NavDestination> (类似哈希表)来存储所有子节点。这个集合以每个 NavDestinationid 为键,使得查找操作非常高效。这也是为什么我们在 navigate() 时传入的是 @IdRes 资源 ID

追问:NavigatorProvider 是在哪里初始化的,它怎么知道 navigation 对应 NavGraphNavigator?
NavigatorProvider 的初始化发生在 NavHostController(NavController 的子类)的构造方法中。在初始化时,系统会默认注册 NavGraphNavigatorActivityNavigatorFragmentNavigator 等。每个 Navigator 通过其注解或内部定义来声明它负责处理的 XML 标签名,例如 NavGraphNavigator 对应的就是 navigation 标签

startDestinationId 启动页是如何被加载的

FragmentDestinationNavGraphFragmentNavigatorNavGraphNavigatorNavigatorProviderNavInflaterNavControllerNavHostFragmentFragmentDestinationNavGraphFragmentNavigatorNavGraphNavigatorNavigatorProviderNavInflaterNavControllerNavHostFragment阶段一:解析 XML 生成路由表阶段二:setGraph 末尾自动加载启动页setGraph(R.navigation.main)inflate()getNavigator("navigation")返回 NavGraphNavigatorcreateDestination() & onInflate()返回 NavGraph 对象getNavigator("fragment")返回 FragmentNavigatorcreateDestination()返回 FragmentDestinationaddDestination() 加入 SparseArray返回完整 NavGraphnavigate( getStartDestinationId() )findNode(startDestId)返回 FragmentDestinationgetNavigatorName()返回 "fragment"getNavigator("fragment")返回 FragmentNavigator 实例navigate(destination, args)FragmentTransaction.replace()加载完成

旧栈会被清空。 在 setGraph() 的开头,源码会主动调用 popBackStackInternal(null, true),把所有旧的回退栈条目全部清除。这意味着新设置的 startDestination 一定是新栈底的唯一页面,用户按返回键不会回到旧图里的页面。 即 先清空栈才会调用 NavController.navigate()

NavController.navigate() 简化逻辑

与启动页加载的方式是几乎一致的

FragmentManagerFragmentNavigatorNavigatorProviderNavGraphNavController调用方FragmentManagerFragmentNavigatorNavigatorProviderNavGraphNavController调用方navigate(destinationId)findNode(destinationId)NavDestinationgetNavigator("fragment")FragmentNavigatornavigate(destination)FragmentTransaction.replace()addToBackStack()更新 BackQueue

简化源码:

// NavController.java (源码简化)
private void navigate(@IdRes int resId, Bundle args, NavOptions options, Navigator.Extras extras) {
    // 1. 从当前 NavGraph 的 SparseArray 中查找到目标节点
    NavDestination destination = mGraph.findNode(resId, false);
    
    // ⭐ 2. 关键:通过目标节点获取导航器名称 (例如 "fragment")
    String navigatorName = destination.getNavigatorName();
    
    // ⭐ 3. 关键:通过 NavigatorProvider 根据名称取到真正的导航器实例
    Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(navigatorName);
    
    // 4. 调用导航器的 navigate 方法执行跳转
    navigator.navigate(destination, args, options, extras);
}

跳转到 navigation 页面

对于 navigate 到一个 Activity 最后会 调用 ActivityNavigator.navigate(), 然后根据转入的 destination 调用 ActivityCompat.startActivity() 来启动目标 Activity

缺点

  • 需要在 nav_graph.xml 提前配置所有页面节点,不易维护. 只有当时写得人容易看懂,而后续维护的人会很费时间。 而且页面多起来时,会导致文件过于臃肿
  • Fragment 类型的路由导航使用的replace,会使得页面频繁重建,体验不够友好
  • Fragment singleTop启动模式,即便目标页在栈顶,依然会销毁后再重建,体验不友好
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值