美文网首页
Android Navigation框架源码学习

Android Navigation框架源码学习

作者: 愤怒的国足 | 来源:发表于2019-06-20 20:26 被阅读0次

    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相比并没有明显的区别,开发效率的影响却比较明显。

    相关文章

      网友评论

          本文标题:Android Navigation框架源码学习

          本文链接:https://www.haomeiwen.com/subject/asiaxctx.html