Android Navigation框架源码学习

0 概述

Navigation框架是Jetpack里面的fragment管理框架,将fragment之间的跳转、动画、栈管理等做了统一的封装,并且跳转的关系可以可视化。使用这个框架可以帮助我们处理一些Fragment跳转常见的问题。

相信之前做过类似单Activity多Fragment的技术选型,应用的都会有切身的体会,坑真的很多,尤其是fragment任务栈的管理,Navigation框架如何完美的处理这些问题的,相信会对我们之后的开发有些帮助与提示。

1 跳转

@SuppressWarnings("deprecation")
    public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras) {
        ....
        //目标节点的ID
        @IdRes int destId = resId;
        final NavAction navAction = currentNode.getAction(resId);
        Bundle combinedArgs = null;
        if (navAction != null) {
            ...
            //如果传入的是actionID,根据ActionID拿到页面的ID
            destId = navAction.getDestinationId();
            ...
        }
      
        //处理参数和一些异常
        ....
        //获取NavDestination对象
        NavDestination node = findDestination(destId);
        ....
        navigate(node, combinedArgs, navOptions, navigatorExtras);
    }
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        ....
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                node.getNavigatorName());
        Bundle finalArgs = node.addInDefaultArgs(args);
        //Navigator的子类有ActivityNavigator/FragmentNavigator等
        //通过子类的Navigator.Name注解来获取对应的子类navigator
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        //处理stack
        if (newDest != null) {
            ......
            // Add all of the remaining parent NavGraphs that aren't
            // already on the back stack
            mBackStack.addAll(hierarchy);
            // And finally, add the new destination
            NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, finalArgs);
            mBackStack.add(newBackStackEntry);
        }
        if (popped || newDest != null) {
            dispatchOnDestinationChanged();
        }
    }

所以Navigation框架不仅可以支持Fragment跳转,Activity也可以用,不过好像没什么必要。
点开用的最多的FragmentNavigator.navigate()方法,不出意外是通过反射拿到Fragment的对象,然后通过FragmentTransaction处理页面和参数、动画。

public NavDestination navigate(@NonNull FragmentNavigator.Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable androidx.navigation.Navigator.Extras navigatorExtras) {
        //从这里可以看到,如果当前页面在后台的时候调用了navigate,会直接被ignore,算是个bug吧,自己要处理下。
        if (this.mFragmentManager.isStateSaved()) {
            Log.i("FragmentNavigator", "Ignoring navigate() call: FragmentManager has already saved its state");
            return null;
        } else {
            String className = destination.getClassName();
            if (className.charAt(0) == '.') {
                className = this.mContext.getPackageName() + className;
            }

            Fragment frag = this.instantiateFragment(this.mContext, this.mFragmentManager, className, args);
            frag.setArguments(args);
            FragmentTransaction ft = this.mFragmentManager.beginTransaction();
            ....
            if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
                ....
                ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
            }

            ft.replace(this.mContainerId, frag);
            .....
            //添加到FragmentManger中的返回栈里
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));

            ft.setReorderingAllowed(true);
            ft.commit();
            if (isAdded) {
                this.mBackStack.add(destId);
                return destination;
            } else {
                return null;
            }
        }
    }

ActivityNavigator.navigate()通过intent和startActivity处理跳转,这里就不贴代码了
整个的跳转先是在NavController里面解析配置文件的节点信息,处理返回栈,然后选择对应的Navigtor进行页面加载。

2 回退栈处理

NavController提供了PopBackStack方法用于退出Fragment

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    //如果直接调的poBackStack()没有传参数,destinationId即为currentDestinationId.
    boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
        if (mBackStack.isEmpty()) {
            // Nothing to pop if the back stack is empty
            return false;
        }

        ArrayList<Navigator> popOperations = new ArrayList<>();
        Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
        boolean foundDestination = false;
        while (iterator.hasNext()) {
            NavDestination destination = iterator.next().getDestination();
            Navigator navigator = mNavigatorProvider.getNavigator(destination.getNavigatorName());
            //将需要退栈的ID放入list中
            if (inclusive || destination.getId() != destinationId) {
                popOperations.add(navigator);
            }
            //编译到destionationId为止
            if (destination.getId() == destinationId) {
                foundDestination = true;
                break;
            }
        }
        if (!foundDestination) {
            ....
            //处理异常
        }
        boolean popped = false;
        //遍历list进行退栈
        for (Navigator navigator : popOperations) {
            //navigator.popBackStack()进行实际的退栈操作,一个个退栈
            if (navigator.popBackStack()) {
                mBackStack.removeLast();
                popped = true;
            } else {
                // The pop did not complete successfully, so stop immediately
                break;
            }
        }
        return popped;
    }

我们还是专注于FragmentNavigator,看下该类中的popBackStack()方法

@Override
    public boolean popBackStack() {
        if (mBackStack.isEmpty()) {
            return false;
        }
        if (mFragmentManager.isStateSaved()) {
            //如果应用程序在后台,popBack和navigate一样也会被忽略
            return false;
        }
        if (mFragmentManager.getBackStackEntryCount() > 0) {
            //通过Fragment的popBackStack完成退栈操作
            mFragmentManager.popBackStack(generateBackStackName(mBackStack.size(), mBackStack.peekLast()),FragmentManager.POP_BACK_STACK_INCLUSIVE);
            mIsPendingBackStackOperation = true;
        } // else, we're on the first Fragment, so there's nothing to pop from FragmentManager
        mBackStack.removeLast();
        return true;
    }

Navigation框架的回退栈也是通过FragmentManger的addToBackStack()和popBackStack()的系统方法去处理的,这种方案可以直接支持物理返回键。

如果导航栏用系统的ActionBar等,可以直接用NavigationUI.setupActionBarWithNavController这些方法。
关于对回退栈的监听,可以看到Navcontroller中的mNavigatorProvider里面有

private final NavigatorProvider mNavigatorProvider = new NavigatorProvider() {
        @Nullable
        @Override
        public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
                @NonNull Navigator<? extends NavDestination> navigator) {
            Navigator<? extends NavDestination> previousNavigator =
                    super.addNavigator(name, navigator);
            if (previousNavigator != navigator) {
                if (previousNavigator != null) {
                    previousNavigator.removeOnNavigatorBackPressListener(mOnBackPressListener);
                }
                navigator.addOnNavigatorBackPressListener(mOnBackPressListener);
            }
            return previousNavigator;
        }
    };

addOnNavigatorBackPressListener看方法名就很明显了,FragmentNavigator的基类Navigator中调用 onBackPressAdded();方法,该方法只在FragmentNavigator被重写了

@Override
    protected void onBackPressAdded() {
        mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
    }

FragmentManager有一个addOnBackStackChangedListener的监听,当BackStack发生变化时会触发,包括添加和移除。

3 总结

整个框架以NavController为核心控制器,负责处理逻辑和调用下面几个部分

  • NavDestination相关联的类和子类负责将xml配置文件中的id与具体的Fragment或者Activity相关联起来,屏蔽细节给NavController跳转用。
  • Navigator相关子类以及NavigatorProvider,是跳转和栈管理、动画处理等这些具体功能的实现者。
  • NavHostFragment继承了fragment作为布局的一部分放入我们的xml中,提供findNavController方法获取对应的Controller实例,作为布局和逻辑的桥梁。

最后

关于Fragment和Activity,Fragment的好处是灵活和资源消耗小,比如一个大的Activity有多个Fragment组合而成,想把一部分页面挪到另一部分,或者想把一个独立页面放到一个Tab上去,Fragment都可以轻松完成。Activity作为系统四大组件之一,对于开发来说功能齐备,安全可靠。
个人比较倾向于多Activity多Fragment的设计模式,兼顾灵活性和可维护性,单Activity多fragment虽然有Navigation这样的官方框架,使用下来感觉坑还是很多,而且个人感觉从用户角度来说Fragment跳转和Activity相比并没有明显的区别,开发效率的影响却比较明显。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容