Jetpack--Navigation原理

Jetpack--Navigation原理

Jetpack向开发者提供了导航组件来实现Activity或者Fragment的跳转,今天就分析一下使用Navigation来实现Fragment之间的跳转。

一、相关类介绍

1. NavHost

NavHost的定义如下:

public interface NavHost {

    /**
     * Returns the {@link NavController navigation controller} for this navigation host.
     *
     * @return this host's navigation controller
     */
    @NonNull
    NavController getNavController();
}

NavHost只要求其实现类必须能够通过getNavController()方法返回一个NavController对象。

而常用的NavHostFragment即为NavHost的一个实现类

public class NavHostFragment extends Fragment implements NavHost {
    ...
    private NavHostController mNavController;
    private View mViewParent;

    // State that will be saved and restored
    private int mGraphId;
    private boolean mDefaultNavHost;
    
    @CallSuper
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = requireContext();

        mNavController = new NavHostController(context);
        mNavController.setLifecycleOwner(this);
        mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
        // Set the default state - this will be updated whenever
        // onPrimaryNavigationFragmentChanged() is called
        mNavController.enableOnBackPressed(
                mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
        mIsPrimaryBeforeOnCreate = null;
        mNavController.setViewModelStore(getViewModelStore());
        onCreateNavController(mNavController);

        ....
    }
}

可见,NavHostFragment返回的NavController的运行时类型为NavHostController

同时需要注意的是,NavHostFragment初始化NavController的时机在onCreate方法之中,再次之前调用getNavController方法返回的只是null

2、NavController

该类是一个核心类,其内部含有一个导航图对象(NavGraph),该导航图通常是通过NavInflater解析navigation资源文件生成的。NavController通过持有的导航图来控制导航行为,顾名思义,该对象就是一个控制器,控制着导航图、对Fragment或Activity生命周期的管理等,一些核心的属性如下:

public class NavController {
    private final Context mContext;
    private Activity mActivity;
    private NavInflater mInflater;      // 负责解析导航文件的解析器
    
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    NavGraph mGraph;    // NavInflater解析导航资源文件生成的导航图
    
    private NavigatorProvider mNavigatorProvider = new NavigatorProvider();     // 该类只是一个辅助类,负责保存用到的Navigator对象
    
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Deque mBackStack = new ArrayDeque<>();

    private LifecycleOwner mLifecycleOwner;
    private NavControllerViewModel mViewModel;

    private NavigatorProvider mNavigatorProvider = new NavigatorProvider();

    private final LifecycleObserver mLifecycleObserver = new LifecycleEventObserver() {
        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (mGraph != null) {
                for (NavBackStackEntry entry : mBackStack) {
                    entry.handleLifecycleEvent(event);
                }
            }
        }
    };
}

通过上小结指导,NavHostFragment的getNavController()返回的NavController对象的运行时类型为:NavHostController

public class NavHostController extends NavController {

    public NavHostController(@NonNull Context context) {
        super(context);
    }

    @Override
    public final void setLifecycleOwner(@NonNull LifecycleOwner owner) {
        super.setLifecycleOwner(owner);
    }

    @Override
    public final void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
        super.setOnBackPressedDispatcher(dispatcher);
    }

    @Override
    public final void enableOnBackPressed(boolean enabled) {
        super.enableOnBackPressed(enabled);
    }

    @Override
    public final void setViewModelStore(@NonNull ViewModelStore viewModelStore) {
        super.setViewModelStore(viewModelStore);
    }
}

观察NavHostController的源码发现,其并没有什么自己的内容,各个方法全部都是父类的实现,对于该类,我们可以暂且忘记,仅仅当做就是NavController对象即可。

3、Navigator

NavController只是管理导航图,导航动作开始于其内部,但是导航动作根本发生地却是在Navigator之中,可以说,Navigator是真正的导航器。Navigator是真正执行导航动作(页面跳转)的地方,其为开发者自动维护了导航所涉及的前后状态

public abstract class Navigator {
    @Retention(RUNTIME)
    @Target({TYPE})
    @SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120
    public @interface Name {
        String value();
    }

    @NonNull
    public abstract D createDestination();

    @Nullable
    public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);

    public abstract boolean popBackStack();

    @Nullable
    public Bundle onSaveState() {
        return null;
    }

    public void onRestoreState(@NonNull Bundle savedState) {
    }

    public interface Extras {
    }
}

可见,Navigator负责的任务主要就是创建目的地,以及navigate到目标页面。

Google为开发者提供了一些Navigator,在第一部分的NavHostFragment的onCreate()方法中涉及到NavController对象的初始化,以及NavController初始化完毕后调用了方法onCreateNavController(mNavController),这两处都初始化并保存了一些Navigator对象

NavHostFragment:onCreate()
public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ....

        mNavController = new NavHostController(context);  // NavController构造函数中添加了一些Navigator
        ....
        onCreateNavController(mNavController);   // 该方法内部也添加了一些Navigator

        ....
    }

我们先来看看第二个地方的代码。

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

@Deprecated
    @NonNull
    protected Navigator createFragmentNavigator() {
        return new FragmentNavigator(requireContext(), getChildFragmentManager(),
                getContainerId());
    }

可见在该方法内部实例化并添加了两个Navigator的实现类:

  • DialogFragmentNavigator
  • FragmentNavigator

而通过名字就能知道,他们一个是负责DialogFragment导航,一个是负责Fragment导航的。

NavController:NavController()
public NavController(@NonNull Context context) {
        mContext = context;
        while (context instanceof ContextWrapper) {
            if (context instanceof Activity) {
                mActivity = (Activity) context;
                break;
            }
            context = ((ContextWrapper) context).getBaseContext();
        }
        mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
        mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
    }

而在NavController的构造函数中也添加了两种Navigator:

  • NavGraphNavigator
  • ActivityNavigator

可以肯定后者是为了Activity之间的导航提供支持,而前者猜测是为了为navigation资源文件中导航嵌套之间的导航提供支持的。

同时也可以知道,这两种Navigator初始化的位置位于NavController的构造器中,而与Fragment导航相关的构造器的添加则位于NavHostFragment之中,合理而又分明。

4、辅助类

NavigatorProvider

该类是一个辅助类,作用只是保存NavController使用到的Navigator对象,在上面的展示中也显示了NavController都是通过其mNavigatorProvider属性来操控Navigator对象的。

public class NavigatorProvider {
    ....

    private final HashMap> mNavigators =
            new HashMap<>();

    @NonNull
    public final > T getNavigator(@NonNull Class navigatorClass) {
        String name = getNameForNavigator(navigatorClass);
        return getNavigator(name);
    }
    
    @SuppressWarnings("unchecked")
    @CallSuper
    @NonNull
    public > T getNavigator(@NonNull String name) {
        if (!validateName(name)) {
            throw new IllegalArgumentException("navigator name cannot be an empty string");
        }

        Navigator navigator = mNavigators.get(name);
        if (navigator == null) {
            throw new IllegalStateException("Could not find Navigator with name \"" + name
                    + "\". You must call NavController.addNavigator() for each navigation type.");
        }
        return (T) navigator;
    }

    @Nullable
    public final Navigator addNavigator(
            @NonNull Navigator navigator) {
        String name = getNameForNavigator(navigator.getClass());

        return addNavigator(name, navigator);
    }

    @CallSuper
    @Nullable
    public Navigator addNavigator(@NonNull String name,
            @NonNull Navigator navigator) {
        if (!validateName(name)) {
            throw new IllegalArgumentException("navigator name cannot be an empty string");
        }
        return mNavigators.put(name, navigator);
    }
}

很清楚,NavigatorProvider内部通过一个HashMap来保存Navigator对象,key为Navigator的类名

NavDestination

根据名字就能够知道,该类代表着navigation资源文件中action中定义的destination fragment(这里的destination fragment的含义为导航图xml文件中作为destination的fragment,即一个destination fragment可能含有多个action,也即一个destination fragment可以通过多个action指定app:destination属性跳转到多个其他的destination fragment);而显然,每一个NavDestination都应该隶属于一个NavGraph

public class NavDestination {
    ....
    private final String mNavigatorName;
    private NavGraph mParent;       // 指明该NavDestination隶属于的NavGraph对象
    private int mId;
    private String mIdName;
    
    
    private SparseArrayCompat mActions;   // 每一个Destination对应的Fragment可能含有多个Action
    private HashMap mArguments;
}

NavGraph

该对象代表了一个导航图,其内部保存了所有的NavDestination节点,前面说到,NavController内部持有一个NavGraph,而NavGraph在NavController内部的作用就是在NavController发出导航指令的时候,NavGraph通过指令传递的id来找到对应的NavDestination节点

public class NavGraph extends NavDestination implements Iterable {
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final SparseArrayCompat mNodes = new SparseArrayCompat<>();
    private int mStartDestId;
    private String mStartDestIdName;
    
    public final void addDestination(@NonNull NavDestination node) {
        if (node.getId() == 0) {
            throw new IllegalArgumentException("Destinations must have an id."
                    + " Call setId() or include an android:id in your navigation XML.");
        }
        NavDestination existingDestination = mNodes.get(node.getId());
        if (existingDestination == node) {
            return;
        }
        if (node.getParent() != null) {
            throw new IllegalStateException("Destination already has a parent set."
                    + " Call NavGraph.remove() to remove the previous parent.");
        }
        if (existingDestination != null) {
            existingDestination.setParent(null);
        }
        node.setParent(this);
        mNodes.put(node.getId(), node);
    }

    public final void addDestinations(@NonNull Collection nodes) {
        for (NavDestination node : nodes) {
            if (node == null) {
                continue;
            }
            addDestination(node);
        }
    }

    public final void addDestinations(@NonNull NavDestination... nodes) {
        for (NavDestination node : nodes) {
            if (node == null) {
                continue;
            }
            addDestination(node);
        }
    }

    @Nullable
    public final NavDestination findNode(@IdRes int resid) {
        return findNode(resid, true);
    }

    @Nullable
    final NavDestination findNode(@IdRes int resid, boolean searchParents) {
        NavDestination destination = mNodes.get(resid);
        // Search the parent for the NavDestination if it is not a child of this navigation graph
        // and searchParents is true
        return destination != null
                ? destination
                : searchParents && getParent() != null ? getParent().findNode(resid) : null;
    }
}

首先可以看到,mNodes对象就是保存该导航图下所有NavDestination节点的容器。而且,NavGraph也提供了一些addDestination和findNode的方法用来保存和获取NavDestination对象。

注:NavGraph的addDestination()方法的调用时机为NavHostFragment的onInflate中,在解析导航资源文件的时候会直接将解析得到的NavDestination加入到NavGraph中。

这里有一个稍微有一些绕的地方,那就是NavGraph的作用是保存导航图中的NavDestination节点,但是他自己本身却是NavDestination的子类,总归有些奇怪。个人认为,这是一种不得已,因为在导航图的资源文件中可以存在navigation嵌套的情况,而这种情况下势必存在一个NavGraph作为NavDestination的角色出现,所以NavGraph是NavDestination就显得不得已而为之了。

NavAction

上面提到一个NavDestination也即一个可能的目的地,而一个目的地又可以有多个Action跳转到其他的目的地,因此,在NavDestination中含有保存所有其下action的属性。而该类显然就是navigation资源文件中的Action的封装了,且看:

public final class NavAction {
    @IdRes
    private final int mDestinationId;
    private NavOptions mNavOptions;
    private Bundle mDefaultArguments;
    ....
}

根据变量就能够知道上述变量的作用。

NavOptions

该类为navigation资源文件中每一个Action标签下的一些选项属性:

public final class NavOptions {
    private boolean mSingleTop;
    @IdRes
    private int mPopUpTo;
    private boolean mPopUpToInclusive;
    @AnimRes @AnimatorRes
    private int mEnterAnim;
    @AnimRes @AnimatorRes
    private int mExitAnim;
    @AnimRes @AnimatorRes
    private int mPopEnterAnim;
    @AnimRes @AnimatorRes
    private int mPopExitAnim;
}

小结

经过上述的介绍,我们可以简单得到一个UML图:


未命名文件 (3).png

可以很明显的看出NavController的管理作用:

  • NavGraph管理着navigation资源文件中定义的destination fragment,并将其封装成NavDestination;而NavDestination则将资源文件中的action标签封装成NavAction对象,而NavAction对象中包含了可以在资源文件中定义的各个options设置;
  • Navigator则负责导航动作的执行,该对象通过NavigatorProvider对象的存取功能向NavController提供服务。
  • NavController为导航发起的位置,协调NavGraph与Navigator共同完成导航功能。

二、导航原理

在使用导航组件进行导航时,简单用法如下:

NavHostFragment navHost =(NavHostFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment_jounal_item_detail);     navHost.getNavController().navigate(R.id.action_DetailFragment_to_ImageShowFragment, bundle);

其中要求layout文件中id对应的fragment必须为Google提供的NavHostFragment,因为目前只有该Fragment对象实现了NavHost接口,才能初始化NavController。

执行跳转时,很显然调用的是NavHostController的navigate方法,而上面展示到NavHostController并没有对父类NavController中的方法进行重写,只是一个空壳子类,所以navigate方法的实现需要看NavController。

1、NavController::navigate

经过回调,最终会调用到含四个参数的navigate方法:

public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras) {
    //  --------------------注释 1-----------------------
        NavDestination currentNode = mBackStack.isEmpty()
                ? mGraph
                : mBackStack.getLast().getDestination();
        if (currentNode == null) {
            throw new IllegalStateException("no current navigation node");
        }
        @IdRes int destId = resId;
        final NavAction navAction = currentNode.getAction(resId);
    
    //  ----------------------注释 2-----------------------------
        Bundle combinedArgs = null;
        if (navAction != null) {
            if (navOptions == null) {
                // ------------------a---------------------
                navOptions = navAction.getNavOptions();
            }
            destId = navAction.getDestinationId();
            Bundle navActionArgs = navAction.getDefaultArguments();
            if (navActionArgs != null) {
                combinedArgs = new Bundle();
                // -------------------b---------------------
                combinedArgs.putAll(navActionArgs);
            }
        }

        if (args != null) {
            if (combinedArgs == null) {
                combinedArgs = new Bundle();
            }
            // -----------------c------------------------
            combinedArgs.putAll(args);
        }

        if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) {
            popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
            return;
        }

       ...
        // ---------------------注释 3---------------------------
        NavDestination node = findDestination(destId);
        ...
        navigate(node, combinedArgs, navOptions, navigatorExtras);
    }

该方法的流程被分为三个大的部分:

  1. 注释1处:该部分的意义是首先获取当前位于的destination fragment对应的NavDestination对象,该对象的获取有两种情况:首先是刚启动时,当前的Fragment还是NavHostFragment,此时mBackStack还是空的,因此此时获取到的是mGraph对象;之后,当navigation资源文件中的startDestination对应的NavDestination被添加到mBackStack后,获取当前destination fragment时就是从mBackStack中了。得到了当前destination fragment之后,根据actionid获取到对应的NavAction对象(前面说过,NavDestination中将资源文件中action标签都封装成了NavAction对象保存了起来);
  2. 注释2处:这部分的含义为配置参数,需要配置的参数由NavOptions与Argument,而二者配置的思想是一致的。通过a处的代码可以发现,最终的NavOptions中navigate方法的参数navOptions的优先级是高于NavAction中的Options的(即xml文件中定义的静态Options);而通过bc处的代码可以知道,最终的Argument是结合了navigate方法的参数args和NavAction中的defaultArgument的,但是由于args中的值是后面添加的,所以如果存在重复的话,args会覆盖掉defaultArgument,所以依然是动态添加的内容优先级高
  3. 注释3处:这部分首先来获取本次导航的目的destination fragment对应的NavDestination对象,然后执行另一个navigate方法。

2、NavController::navigate

该方法虽然同名,但是第一个参数变为了目的destination fragment对应的NavDestination:

private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        ....
        Bundle finalArgs = node.addInDefaultArgs(args);
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        if (newDest != null) {
            ...
            // And finally, add the new destination with its default args
            NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
                    newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
            mBackStack.add(newBackStackEntry);
        } else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
            launchSingleTop = true;
            NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
            if (singleTopBackStackEntry != null) {
                singleTopBackStackEntry.replaceArguments(args);
            }
        }
        updateOnBackPressedCallbackEnabled();
        if (popped || newDest != null || launchSingleTop) {
            dispatchOnDestinationChanged();
        }
    }

该方法就显得很简单了:

  1. 首先是调用Navigator对象的navigate方法执行导航动作;

  2. 根据上一步的结果进行处理:

    1. 成功得到了一个NavDestination对象,说明该对象不是singleTop启动或者是singleTop启动但为首次启动,这都说明了该对象应该添加到mBackStack中,第一个if语句就是这个意思;
    2. 而else if则说明是singleTop启动,并且mBackStack中已经有了该NavDestination并且位于top位置,说明是singleTop启动,那么执行的动作是使用replaceArgument来替换掉原来的数据

最后,代码的关键就是看Navigator.navigate了,其内部是如何执行的?以及,何时返回null?

3、Navigator::navigate

根据上面的分析可知,我们本场景下的Navigator的具体类为:FragmentNavigator,为此,我们看看他的navigate方法实现:

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        ....
            // ------------------注释 1---------------------
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        frag.setArguments(args);
        final FragmentTransaction ft = mFragmentManager.beginTransaction();

    // ----------------------注释 2-----------------------
        int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
        int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
        int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
        int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
            popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

    // -------------------注释 3-----------------------
        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

    // ----------------------注释 4-----------------------
        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId;

        boolean isAdded;
    
    // --------------------------a----------------------------
        if (initialNavigation) {
            isAdded = true;
        } else if (isSingleTopReplacement) {
            // --------------------------b----------------------------
            // Single Top means we only want one instance on the back stack
            if (mBackStack.size() > 1) {
                // If the Fragment to be replaced is on the FragmentManager's
                // back stack, a simple replace() isn't enough so we
                // remove it from the back stack and put our replacement
                // on the back stack in its place
                mFragmentManager.popBackStack(
                        generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
            }
            isAdded = false;
        } else {
            // --------------------------c----------------------------
            ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
            isAdded = true;
        }
        ....
        ft.setReorderingAllowed(true);
        ft.commit();
    
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

对于该方法,明显也可以分为如下几个部分:

  1. 步骤1:该部分很明确,就是通过类名利用反射来创建一个Fragment对象,然后为Fragment设置Argument,同时开启一个Fragment事务
  2. 步骤2:这部分的含义是为Fragment配置动画等信息;
  3. 步骤3:利用Fragment事务执行replace方法,将目标Fragment替换到页面上;
  4. 步骤4:这一步又是专门对singleTop启动的处理,首先isSingleTopReplacement变量的含义为:当前导航的启动模式为singleTop,并且目destination fragment就在mBackStack的top位置,所以此时调用了FragmentManager的popBackStack来撤销上一次的replace事务操作(根据意义可知上一次replace操作证实将目标destination fragment加入到了Fragment栈中,所以这里的意义是将其从栈中剔除,那么步骤3的replace就会再次将其加入到回退栈中,实现了singleTop),虽说FragmentManager管理的Fragment回退栈中的top位置的Fragment已经重新replace了,但是在Navigator的mBackStack中对应的NavDestination确没有必要剔除再加入新的,所以这一步之后isAdd变量为false,这也导致了在NavController调用Navigator.navigate返回的最终是null,从而引发NavController在管理自己的mBackStack的行为的不同。而ac处的代码就是表明需要在Navigator的mBackStack中添加。

三、总结

总的来说,Navigation组件实现Fragment之间的跳转本质还是通过FragmentManager + FragmentTransaction的组合方式来实现的。

优点

使用导航组件实现Fragment之间的跳转的优点在于:

  1. 逻辑清晰且代码整洁:页面之间的跳转关系定义在navigation资源文件中,使得跳转关系一目了然,避免了写在Activity之中混乱不堪,分布杂乱的问题;同时,封装了的FragmentManager与FragmentTransaction也进一步解放了开发者。
  2. 功能丰富:最明显的就是提供了Fragment跳转的singleTop启动支持,通过上面的分析可以知道Navigation实现Fragment的singleTop功能有两点:首先,利用Navigator的mBackStack数据结构来判断是否存在目标Fragment以及是否已经位于top位置,这是FragmentManager的Fragment回退栈没法做到的事情;其次,实现singleTop功能利用FragmentManager撤销上一次FragmentTransaction和添加新一次FragmentTransaction的replace来实现(同样可以发现,Fragment的singleTop只有当目标Fragment正好位于top位置是才会生效,如果位于回退栈的非top位置,那么便不会有作用)

问题

1. 可以发现Navigator与NavController都各自维护了一个mBackStack,感觉有些多余?二者只维护一个不可以吗?

你可能感兴趣的:(Jetpack--Navigation原理)