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)
Sunon推出新型轴流风扇 适用于要求苛刻的应用
半导体划片机助力氧化铝陶瓷片切割:科技与工艺的完美结合
用什么测试光学镜头的透过率与反射率
海康威视助力推动安徽省的数字经济高质量发展
强悍全大核重新定义游戏体验!天玑9300就是旗舰标杆
Navigation源码解析
相比华为,三星在OLED面板良品率已经将近80%
微软展示全新Win10开始菜单,取消了动态磁贴
步进电机的结构及基本工作原理
小米笔记本15.6寸2019款体验 相比同类型的大屏入门笔记本性价比明显更高
储能系统的应用场景
国辰挂轨巡检机器人解决方案
新能源汽车、智慧物联等领域将给园区新基建板块带来强劲推进力
苹果降低iPhone 8订单量,押宝iPhone X,试图挽回中国市场!
现场总线连接器7/8航空插头
新春佳节你想好送什么了吗?爱国者告诉你满意的答案
!销售/维修SFL-V罗德与施瓦茨SFL-V 小兵/李S
基于凌阳16位单片机的智能车设计
PRBTEK分享无源探头的工作原理
金鉴实验室协助鸿利智汇产品顺利通过AEC-Q102认证