Jetpack--Navigation原理

Jetpack--Navigation原理

Jetpack向開發(fā)者提供了導(dǎo)航組件來實(shí)現(xiàn)Activity或者Fragment的跳轉(zhuǎn),今天就分析一下使用Navigation來實(shí)現(xiàn)Fragment之間的跳轉(zhuǎn)。

一、相關(guān)類介紹

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只要求其實(shí)現(xiàn)類必須能夠通過getNavController()方法返回一個(gè)NavController對象。

而常用的NavHostFragment即為NavHost的一個(gè)實(shí)現(xiàn)類

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的運(yùn)行時(shí)類型為NavHostController。

同時(shí)需要注意的是,NavHostFragment初始化NavController的時(shí)機(jī)在onCreate方法之中,再次之前調(diào)用getNavController方法返回的只是null。

2、NavController

該類是一個(gè)核心類,其內(nèi)部含有一個(gè)導(dǎo)航圖對象(NavGraph),該導(dǎo)航圖通常是通過NavInflater解析navigation資源文件生成的。NavController通過持有的導(dǎo)航圖來控制導(dǎo)航行為,顧名思義,該對象就是一個(gè)控制器,控制著導(dǎo)航圖、對Fragment或Activity生命周期的管理等,一些核心的屬性如下:

public class NavController {
    private final Context mContext;
    private Activity mActivity;
    private NavInflater mInflater;      // 負(fù)責(zé)解析導(dǎo)航文件的解析器
    
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    NavGraph mGraph;    // NavInflater解析導(dǎo)航資源文件生成的導(dǎo)航圖
    
    private NavigatorProvider mNavigatorProvider = new NavigatorProvider();     // 該類只是一個(gè)輔助類,負(fù)責(zé)保存用到的Navigator對象
    
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final Deque<NavBackStackEntry> 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);
                }
            }
        }
    };
}

通過上小結(jié)指導(dǎo),NavHostFragment的getNavController()返回的NavController對象的運(yùn)行時(shí)類型為: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的源碼發(fā)現(xiàn),其并沒有什么自己的內(nèi)容,各個(gè)方法全部都是父類的實(shí)現(xiàn),對于該類,我們可以暫且忘記,僅僅當(dāng)做就是NavController對象即可。

3、Navigator

NavController只是管理導(dǎo)航圖,導(dǎo)航動作開始于其內(nèi)部,但是導(dǎo)航動作根本發(fā)生地卻是在Navigator之中,可以說,Navigator是真正的導(dǎo)航器。Navigator是真正執(zhí)行導(dǎo)航動作(頁面跳轉(zhuǎn))的地方,其為開發(fā)者自動維護(hù)了導(dǎo)航所涉及的前后狀態(tài)。

public abstract class Navigator<D extends NavDestination> {
    @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負(fù)責(zé)的任務(wù)主要就是創(chuàng)建目的地,以及navigate到目標(biāo)頁面。

Google為開發(fā)者提供了一些Navigator,在第一部分的NavHostFragment的onCreate()方法中涉及到NavController對象的初始化,以及NavController初始化完畢后調(diào)用了方法onCreateNavController(mNavController),這兩處都初始化并保存了一些Navigator對象

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

        mNavController = new NavHostController(context);  // NavController構(gòu)造函數(shù)中添加了一些Navigator
        ....
        onCreateNavController(mNavController);   // 該方法內(nèi)部也添加了一些Navigator

        ....
    }

我們先來看看第二個(gè)地方的代碼。

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<? extends FragmentNavigator.Destination> createFragmentNavigator() {
        return new FragmentNavigator(requireContext(), getChildFragmentManager(),
                getContainerId());
    }

可見在該方法內(nèi)部實(shí)例化并添加了兩個(gè)Navigator的實(shí)現(xiàn)類:

  • DialogFragmentNavigator
  • FragmentNavigator

而通過名字就能知道,他們一個(gè)是負(fù)責(zé)DialogFragment導(dǎo)航,一個(gè)是負(fù)責(zé)Fragment導(dǎo)航的。

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的構(gòu)造函數(shù)中也添加了兩種Navigator:

  • NavGraphNavigator
  • ActivityNavigator

可以肯定后者是為了Activity之間的導(dǎo)航提供支持,而前者猜測是為了為navigation資源文件中導(dǎo)航嵌套之間的導(dǎo)航提供支持的。

同時(shí)也可以知道,這兩種Navigator初始化的位置位于NavController的構(gòu)造器中,而與Fragment導(dǎo)航相關(guān)的構(gòu)造器的添加則位于NavHostFragment之中,合理而又分明。

4、輔助類

NavigatorProvider

該類是一個(gè)輔助類,作用只是保存NavController使用到的Navigator對象,在上面的展示中也顯示了NavController都是通過其mNavigatorProvider屬性來操控Navigator對象的。

public class NavigatorProvider {
    ....

    private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
            new HashMap<>();

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

        Navigator<? extends NavDestination> 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<? extends NavDestination> addNavigator(
            @NonNull Navigator<? extends NavDestination> navigator) {
        String name = getNameForNavigator(navigator.getClass());

        return addNavigator(name, navigator);
    }

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

很清楚,NavigatorProvider內(nèi)部通過一個(gè)HashMap來保存Navigator對象,key為Navigator的類名。

NavDestination

根據(jù)名字就能夠知道,該類代表著navigation資源文件中action中定義的destination fragment(這里的destination fragment的含義為導(dǎo)航圖xml文件中作為destination的fragment,即一個(gè)destination fragment可能含有多個(gè)action,也即一個(gè)destination fragment可以通過多個(gè)action指定app:destination屬性跳轉(zhuǎn)到多個(gè)其他的destination fragment);而顯然,每一個(gè)NavDestination都應(yīng)該隸屬于一個(gè)NavGraph

public class NavDestination {
    ....
    private final String mNavigatorName;
    private NavGraph mParent;       // 指明該NavDestination隸屬于的NavGraph對象
    private int mId;
    private String mIdName;
    
    
    private SparseArrayCompat<NavAction> mActions;   // 每一個(gè)Destination對應(yīng)的Fragment可能含有多個(gè)Action
    private HashMap<String, NavArgument> mArguments;
}

NavGraph

該對象代表了一個(gè)導(dǎo)航圖,其內(nèi)部保存了所有的NavDestination節(jié)點(diǎn),前面說到,NavController內(nèi)部持有一個(gè)NavGraph,而NavGraph在NavController內(nèi)部的作用就是在NavController發(fā)出導(dǎo)航指令的時(shí)候,NavGraph通過指令傳遞的id來找到對應(yīng)的NavDestination節(jié)點(diǎn)。

public class NavGraph extends NavDestination implements Iterable<NavDestination> {
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final SparseArrayCompat<NavDestination> 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<NavDestination> 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對象就是保存該導(dǎo)航圖下所有NavDestination節(jié)點(diǎn)的容器。而且,NavGraph也提供了一些addDestination和findNode的方法用來保存和獲取NavDestination對象。

注:NavGraph的addDestination()方法的調(diào)用時(shí)機(jī)為NavHostFragment的onInflate中,在解析導(dǎo)航資源文件的時(shí)候會直接將解析得到的NavDestination加入到NavGraph中。

這里有一個(gè)稍微有一些繞的地方,那就是NavGraph的作用是保存導(dǎo)航圖中的NavDestination節(jié)點(diǎn),但是他自己本身卻是NavDestination的子類,總歸有些奇怪。個(gè)人認(rèn)為,這是一種不得已,因?yàn)樵趯?dǎo)航圖的資源文件中可以存在navigation嵌套的情況,而這種情況下勢必存在一個(gè)NavGraph作為NavDestination的角色出現(xiàn),所以NavGraph是NavDestination就顯得不得已而為之了。

NavAction

上面提到一個(gè)NavDestination也即一個(gè)可能的目的地,而一個(gè)目的地又可以有多個(gè)Action跳轉(zhuǎn)到其他的目的地,因此,在NavDestination中含有保存所有其下action的屬性。而該類顯然就是navigation資源文件中的Action的封裝了,且看:

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

根據(jù)變量就能夠知道上述變量的作用。

NavOptions

該類為navigation資源文件中每一個(gè)Action標(biāo)簽下的一些選項(xiàng)屬性:

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;
}

小結(jié)

經(jīng)過上述的介紹,我們可以簡單得到一個(gè)UML圖:


未命名文件 (3).png

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

  • NavGraph管理著navigation資源文件中定義的destination fragment,并將其封裝成NavDestination;而NavDestination則將資源文件中的action標(biāo)簽封裝成NavAction對象,而NavAction對象中包含了可以在資源文件中定義的各個(gè)options設(shè)置;
  • Navigator則負(fù)責(zé)導(dǎo)航動作的執(zhí)行,該對象通過NavigatorProvider對象的存取功能向NavController提供服務(wù)。
  • NavController為導(dǎo)航發(fā)起的位置,協(xié)調(diào)NavGraph與Navigator共同完成導(dǎo)航功能。

二、導(dǎo)航原理

在使用導(dǎo)航組件進(jìn)行導(dǎo)航時(shí),簡單用法如下:

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對應(yīng)的fragment必須為Google提供的NavHostFragment,因?yàn)槟壳爸挥性揊ragment對象實(shí)現(xiàn)了NavHost接口,才能初始化NavController。

執(zhí)行跳轉(zhuǎn)時(shí),很顯然調(diào)用的是NavHostController的navigate方法,而上面展示到NavHostController并沒有對父類NavController中的方法進(jìn)行重寫,只是一個(gè)空殼子類,所以navigate方法的實(shí)現(xiàn)需要看NavController。

1、NavController::navigate

經(jīng)過回調(diào),最終會調(diào)用到含四個(gè)參數(shù)的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);
    }

該方法的流程被分為三個(gè)大的部分:

  1. 注釋1處:該部分的意義是首先獲取當(dāng)前位于的destination fragment對應(yīng)的NavDestination對象,該對象的獲取有兩種情況:首先是剛啟動時(shí),當(dāng)前的Fragment還是NavHostFragment,此時(shí)mBackStack還是空的,因此此時(shí)獲取到的是mGraph對象;之后,當(dāng)navigation資源文件中的startDestination對應(yīng)的NavDestination被添加到mBackStack后,獲取當(dāng)前destination fragment時(shí)就是從mBackStack中了。得到了當(dāng)前destination fragment之后,根據(jù)actionid獲取到對應(yīng)的NavAction對象(前面說過,NavDestination中將資源文件中action標(biāo)簽都封裝成了NavAction對象保存了起來);
  2. 注釋2處:這部分的含義為配置參數(shù),需要配置的參數(shù)由NavOptions與Argument,而二者配置的思想是一致的。通過a處的代碼可以發(fā)現(xiàn),最終的NavOptions中navigate方法的參數(shù)navOptions的優(yōu)先級是高于NavAction中的Options的(即xml文件中定義的靜態(tài)Options);而通過bc處的代碼可以知道,最終的Argument是結(jié)合了navigate方法的參數(shù)args和NavAction中的defaultArgument的,但是由于args中的值是后面添加的,所以如果存在重復(fù)的話,args會覆蓋掉defaultArgument,所以依然是動態(tài)添加的內(nèi)容優(yōu)先級高;
  3. 注釋3處:這部分首先來獲取本次導(dǎo)航的目的destination fragment對應(yīng)的NavDestination對象,然后執(zhí)行另一個(gè)navigate方法。

2、NavController::navigate

該方法雖然同名,但是第一個(gè)參數(shù)變?yōu)榱四康膁estination fragment對應(yīng)的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. 首先是調(diào)用Navigator對象的navigate方法執(zhí)行導(dǎo)航動作;

  2. 根據(jù)上一步的結(jié)果進(jìn)行處理:

    1. 成功得到了一個(gè)NavDestination對象,說明該對象不是singleTop啟動或者是singleTop啟動但為首次啟動,這都說明了該對象應(yīng)該添加到mBackStack中,第一個(gè)if語句就是這個(gè)意思;
    2. 而else if則說明是singleTop啟動,并且mBackStack中已經(jīng)有了該NavDestination并且位于top位置,說明是singleTop啟動,那么執(zhí)行的動作是使用replaceArgument來替換掉原來的數(shù)據(jù)

最后,代碼的關(guān)鍵就是看Navigator.navigate了,其內(nèi)部是如何執(zhí)行的?以及,何時(shí)返回null?

3、Navigator::navigate

根據(jù)上面的分析可知,我們本場景下的Navigator的具體類為:FragmentNavigator,為此,我們看看他的navigate方法實(shí)現(xiàn):

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;
        }
    }

對于該方法,明顯也可以分為如下幾個(gè)部分:

  1. 步驟1:該部分很明確,就是通過類名利用反射來創(chuàng)建一個(gè)Fragment對象,然后為Fragment設(shè)置Argument,同時(shí)開啟一個(gè)Fragment事務(wù)
  2. 步驟2:這部分的含義是為Fragment配置動畫等信息;
  3. 步驟3:利用Fragment事務(wù)執(zhí)行replace方法,將目標(biāo)Fragment替換到頁面上;
  4. 步驟4:這一步又是專門對singleTop啟動的處理,首先isSingleTopReplacement變量的含義為:當(dāng)前導(dǎo)航的啟動模式為singleTop,并且目destination fragment就在mBackStack的top位置,所以此時(shí)調(diào)用了FragmentManager的popBackStack來撤銷上一次的replace事務(wù)操作(根據(jù)意義可知上一次replace操作證實(shí)將目標(biāo)destination fragment加入到了Fragment棧中,所以這里的意義是將其從棧中剔除,那么步驟3的replace就會再次將其加入到回退棧中,實(shí)現(xiàn)了singleTop),雖說FragmentManager管理的Fragment回退棧中的top位置的Fragment已經(jīng)重新replace了,但是在Navigator的mBackStack中對應(yīng)的NavDestination確沒有必要剔除再加入新的,所以這一步之后isAdd變量為false,這也導(dǎo)致了在NavController調(diào)用Navigator.navigate返回的最終是null,從而引發(fā)NavController在管理自己的mBackStack的行為的不同。而ac處的代碼就是表明需要在Navigator的mBackStack中添加。

三、總結(jié)

總的來說,Navigation組件實(shí)現(xiàn)Fragment之間的跳轉(zhuǎn)本質(zhì)還是通過FragmentManager + FragmentTransaction的組合方式來實(shí)現(xiàn)的。

優(yōu)點(diǎn)

使用導(dǎo)航組件實(shí)現(xiàn)Fragment之間的跳轉(zhuǎn)的優(yōu)點(diǎn)在于:

  1. 邏輯清晰且代碼整潔:頁面之間的跳轉(zhuǎn)關(guān)系定義在navigation資源文件中,使得跳轉(zhuǎn)關(guān)系一目了然,避免了寫在Activity之中混亂不堪,分布雜亂的問題;同時(shí),封裝了的FragmentManager與FragmentTransaction也進(jìn)一步解放了開發(fā)者。
  2. 功能豐富:最明顯的就是提供了Fragment跳轉(zhuǎn)的singleTop啟動支持,通過上面的分析可以知道Navigation實(shí)現(xiàn)Fragment的singleTop功能有兩點(diǎn):首先,利用Navigator的mBackStack數(shù)據(jù)結(jié)構(gòu)來判斷是否存在目標(biāo)Fragment以及是否已經(jīng)位于top位置,這是FragmentManager的Fragment回退棧沒法做到的事情;其次,實(shí)現(xiàn)singleTop功能利用FragmentManager撤銷上一次FragmentTransaction和添加新一次FragmentTransaction的replace來實(shí)現(xiàn)(同樣可以發(fā)現(xiàn),F(xiàn)ragment的singleTop只有當(dāng)目標(biāo)Fragment正好位于top位置是才會生效,如果位于回退棧的非top位置,那么便不會有作用)

問題

1. 可以發(fā)現(xiàn)Navigator與NavController都各自維護(hù)了一個(gè)mBackStack,感覺有些多余?二者只維護(hù)一個(gè)不可以嗎?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容