0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Navigation源码解析

电子工程师 来源:Android开发之旅 作者:四爷 2021-06-15 16:38 次阅读

Navigation源码解析

谷歌推出Navigation主要是为了统一应用内页面跳转行为。本文主要是根据Navigation版本为2.1.0 的源码进行讲解。

androidx.navigation2.1.0’ ‘androidx.navigation2.1.0’ ‘androidx.navigation2.1.0’ ‘androidx.navigation2.1.0’

Navigation的使用很简单,在创建新项目的时候可以直接选择 Bottom Navigation Activity 项目,这样默认就已经帮我们实现了相关页面逻辑。

Navigation的源码也很简单,但是却涉及到很多的类,主要有以下几个:

Navigation提供查找NavController方法

NavHostFragment用于承载导航的内容的容器

NavController通过navigate实现页面的跳转

Navigator是一个abstract,有四个主要实现类

NavDestination导航节点

NavGraph导航节点页面集合

我们首先从NavHostFragment入手查看,因为他是直接定义在我们的XML文件中的,我们直接查看器生命周期方法 onCreate :

@CallSuper @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext();

mNavController = new NavHostController(context); //1 mNavController.setLifecycleOwner(this);

。。.。

onCreateNavController(mNavController);//2

。。.。 }

注释1处 直接创建了NavHostController 并通过 findNavController 方法暴露给外部调用者。NavHostController是继承自NavController的。注释2处代码如下:

@CallSuper protected void onCreateNavController(@NonNull NavController navController) { navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); }

通过navController获取NavigatorProvider并向其中添加了两个Navigator,分别为DialogFragmentNavigator和FragmentNavigator。另外在NavController的构造方法中还添加了另外两个Navigator,如下:

public NavController(@NonNull Context context) { 。。.。 mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider)); mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));}

他们都是Navigator的实现类。分别对应于DialogFragment、Fragment和Activity的页面跳转。大家可能对于NavGraphNavigator一些好奇,它是用在什么地方的呢?其实我们在XML中配置的navGraph对应的navigation跟节点文件中的 startDestination 就是通过NavGraphNavigator来实现跳转的。这也是它目前唯一的用途。

各个Navigator通过复写 navigate 方法来实现各自的跳转逻辑。这里重点强调下 FragmentNavigator 的实现逻辑:

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {

。。.。

final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args); frag.setArguments(args); final FragmentTransaction ft = mFragmentManager.beginTransaction();

。。.。

ft.replace(mContainerId, frag); //1

。。.。}

最关键的一行代码就是注释1 处。他是通过 replace 来加载 Fragment 的 ,这不符合我们实际的开发逻辑。文章后续会讲解如何自定义 FragmentNavigator 来避免 Fragment 在切换的时候 生命周期的执行。

回到上文中的 navController 获取的 NavigatorProvider 其内部是维护了一个HashMap来存储相关的Navigator信息。通过获取到Navigator的注解 Name 为key 和 Navigator 的 getClass为 value 进行存储。

我们在回到上文中的 onCreate 方法:

@CallSuper@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext();

。。.。

if (mGraphId != 0) { mNavController.setGraph(mGraphId); } else {

。。.。

if (graphId != 0) { mNavController.setGraph(graphId, startDestinationArgs); } }}

这里通过 mNavController 调用了 setGraph 。这里主要是为了解析我们的 XML 中配置的mobile_navigation节点信息文件。会根据不同的节点来各自解析。

@NonNullprivate NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser, @NonNull AttributeSet attrs, int graphResId) throws XmlPullParserException, IOException {

Navigator navigator = mNavigatorProvider.getNavigator(parser.getName()); final NavDestination dest = navigator.createDestination();

dest.onInflate(mContext, attrs);

。。.。

final String name = parser.getName(); if (TAG_ARGUMENT.equals(name)) { // argument 节点 inflateArgumentForDestination(res, dest, attrs, graphResId); } else if (TAG_DEEP_LINK.equals(name)) { // deeplink 节点 inflateDeepLink(res, dest, attrs); } else if (TAG_ACTION.equals(name)) { // action 节点 inflateAction(res, dest, attrs, parser, graphResId); } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) { // include 节点 final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavInclude); final int id = a.getResourceId(R.styleable.NavInclude_graph, 0); ((NavGraph) dest).addDestination(inflate(id)); a.recycle(); } else if (dest instanceof NavGraph) { // NavGraph 节点 ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId)); } }

return dest;}

通过获取 NavInflater 来对其进行解析。解析后返回 NavGraph ,NavGraph是继承自 NavDestination的。里面主要是保存了所有解析出来的节点信息。

最后简单的总结下就是通过 NavHostFragment 获取到NavContorl并存储了相关的Navigator信息。通过各自的navigate方法进行页面的跳转。通过setGraph来解析配置的页面节点信息,并封装为NavGraph对象。里面通过SparseArray来存储 Destination 信息。

自定义Navigator上文中我们说了需要自定义自己的 Navigator 用于承载 Fragment 。主要的实现思路就是继承现有的 FragmentNavigator 并复写其 navigate 方法,将其中的 replace 方法 替换为 show 和 hide 方法 来完成 Fragment 的切换。

那么我们自定义的 Navigator 如何才能让系统识别呢?这也简单,只要给我们的 类加上注解 @Navigator.Name(value) 那么他就是一个 Navigator 了。最后通过上文中分析的思路 在将其加入到NavigatorProvider 中 即可。

具体的自定义Navigator 已经在项目 Android Jetpack架构开发组件化应用实战(https://github.com/winlee28/Jetpack-WanAndroid) 中了,类名:FixFragmentNavigator。大家可以自行去看下。这里就将核心的代码贴出来看下:

@Navigator.Name(“fixFragment”) //新的 Navigator 名称class FixFragmentNavigator(context: Context, manager: FragmentManager, containerId: Int) : FragmentNavigator(context, manager, containerId) {

override fun navigate( destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Navigator.Extras? ): NavDestination? {

。。.。

//ft.replace(mContainerId, frag)

/** * 1、先查询当前显示的fragment 不为空则将其hide * 2、根据tag查询当前添加的fragment是否不为null,不为null则将其直接show * 3、为null则通过instantiateFragment方法创建fragment实例 * 4、将创建的实例添加在事务中 */ val fragment = mManager.primaryNavigationFragment //当前显示的fragment if (fragment != null) { ft.hide(fragment) }

var frag: Fragment? val tag = destination.id.toString() frag = mManager.findFragmentByTag(tag) if (frag != null) { ft.show(frag) } else { frag = instantiateFragment(mContext, mManager, className, args) frag.arguments = args ft.add(mContainerId, frag, tag) }

。。.。 }}

自定义完成好,还需要将 mobile_navigation 的节点中远 fragment 替换为 fixFragment 节点。并删除布局文件中NavHostFragment 节点的

app:navGraph=“@navigation/mobile_navigation”

信息,因为我们需要手动将 FixFragmentNavigator 和 NavControl 进行关联。

//添加自定义的FixFragmentNavigatornavController = Navigation.findNavController(this, R.id.nav_host_fragment)val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragmentval fragmentNavigator = FixFragmentNavigator(this, supportFragmentManager, fragment!!.id)navController.navigatorProvider.addNavigator(fragmentNavigator)

navController.setGraph(R.navigation.mobile_navigation)

这样就完成了自定义 Navigator 实现切换 Tab 的时候 Fragment 生命周期不会重新执行了。

具体代码逻辑详见:Android Jetpack架构开发组件化应用实战(https://github.com/winlee28/Jetpack-WanAndroid)

编辑:jq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Android
    +关注

    关注

    12

    文章

    3936

    浏览量

    127385
  • XML
    XML
    +关注

    关注

    0

    文章

    188

    浏览量

    33083
  • 源码
    +关注

    关注

    8

    文章

    640

    浏览量

    29200

原文标题:Navigation源码解析及自定义FragmentNavigator详解

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    SSM框架的源码解析与理解

    的核心是控制反转(IoC)和面向切面编程(AOP)。 源码解析: Spring的源码主要分为以下几个部分: Bean容器: 负责实例化、配置和组装对象。核心接口是 B
    的头像 发表于 12-17 09:20 249次阅读

    ESP-BOX 智能药盒源码解析(续)

    药盒基本概述请参考上一篇文章源码解析主函数部分___main/main.c●初始化NVS:初始化非易失性存储(NVS),如果需要擦除和重新初始化NVS,会进行相应处理。●检查百度API密钥:调用
    的头像 发表于 10-10 08:01 227次阅读
    ESP-BOX 智能药盒<b class='flag-5'>源码</b><b class='flag-5'>解析</b>(续)

    如何在NXP源码基础上适配ELF 1开发板的PWM功能

    本次源码适配项目是在NXP i.MX6ULL EVK评估板所搭载的Linux内核源码(版本为Linux-imx_4.1.15)基础上进行的,主要目标是通过调整功能接口引脚配置,使其适应ELF 1开发板。为了深入阐述这一适配过程,我们将以PWM功能的适配作为具体示例,深入
    的头像 发表于 09-10 10:00 929次阅读
    如何在NXP<b class='flag-5'>源码</b>基础上适配ELF 1开发板的PWM功能

    ESP32 崩溃后调试信息定位到源码方法

    arduino 通过调试信息定位出错源码
    的头像 发表于 08-27 14:29 883次阅读

    ElfBoard技术贴|在NXP源码基础上适配ELF 1开发板的按键功能

    ,将以按键功能的适配作为具体示例,深入解析整个适配的流程。一、准备工作NXP源码路径:ELF1开发板资料包\07-NXP原厂资料\07-1NXP官方源码\linux-
    的头像 发表于 07-10 09:54 608次阅读
    ElfBoard技术贴|在NXP<b class='flag-5'>源码</b>基础上适配ELF 1开发板的按键功能

    UCGUI单片机源码

    UCGUI单片机源码
    发表于 07-04 17:11 1次下载

    鸿蒙ArkTS声明式组件:Navigation

    Navigation组件一般作为Page页面的根容器,通过属性设置来展示页面的标题栏、工具栏、导航栏等。
    的头像 发表于 06-26 09:43 1478次阅读
    鸿蒙ArkTS声明式组件:<b class='flag-5'>Navigation</b>

    浙大博导开源飞控planner源码

    浙大博导开源飞控planner源码
    发表于 06-12 11:43 4次下载

    labview实例源码之控压取样系统

    labview源码,包含报表、曲线、通讯等
    发表于 06-06 11:23 1次下载

    什么是源码源码有什么作用?源码组件是什么?源码可二次开发吗?

    源码,也称为源程序,是指未编译的按照一定的程序设计语言规范书写的文本文件,是一系列人类可读的计算机语言指令。
    的头像 发表于 05-25 14:55 1.6w次阅读
    什么是<b class='flag-5'>源码</b>?<b class='flag-5'>源码</b>有什么作用?<b class='flag-5'>源码</b>组件是什么?<b class='flag-5'>源码</b>可二次开发吗?

    HarmonyOS实战开发-如何在Navigation中完成路由拦截

    介绍 本示例介绍在Navigation中如何完成路由拦截:首次登录时记录登录状态,再次登录时可以直接访问主页无需重复登录,当退出登录时,下次需重新登录。 效果图预览 使用说明 点击
    发表于 05-08 14:21

    HarmonyOS开发:【基于命令行(获取源码)】

    在Ubuntu环境下通过以下步骤获取OpenHarmony源码
    的头像 发表于 04-25 22:08 403次阅读
    HarmonyOS开发:【基于命令行(获取<b class='flag-5'>源码</b>)】

    基于Android13的AOSP源码下载及编译指南

    AOSP(Android Open Source Project)是Android操作系统的开源项目,通过下载和编译AOSP源码,您可以获得原始的Android系统,并进行定制和开发。本教程将向您介绍如何下载AOSP源码并进行编译的步骤。
    的头像 发表于 01-17 09:49 3951次阅读
    基于Android13的AOSP<b class='flag-5'>源码</b>下载及编译指南

    Apache Doris聚合函数源码解析

    笔者最近由于工作需要开始调研 Apache Doris,通过阅读聚合函数代码切入 Apache Doris 内核,同时也秉承着开源的精神,开发了 array_agg 函数并贡献给社区。笔者通过这篇文章记录下对源码的一些理解,同时也方便后面的新人更快速地上手源码开发。
    的头像 发表于 01-16 09:52 1022次阅读
    Apache Doris聚合函数<b class='flag-5'>源码</b><b class='flag-5'>解析</b>

    C#网络串口调试助手源码

    非常牛B网络串口调试助手C#源码,支持添加多条协议
    发表于 12-27 09:45 4次下载