美文网首页
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