preface

I first got to know this component at Google IO in ’18, and it was interesting to see them showing visual navigation diagrams, but I didn’t really understand what it was for

I didn’t understand its wonderful use until I used this component in the new project I came into contact with recently. In order to better use and expand the function of Navigation, the author analyzes its implementation principle here

Navigation

For instructions on Navigation, please refer to the Android Developer documentation. Google also provides a sample to learn

role

Once experienced, Navigation can help us solve the following problems

  • Redirect the Activity/Fragment using the ID
    • Reduces the coupling of classes and only cares about ids
    • On this basis, the functions of routing components can also be extended, and the registration process is the combination of navigation charts
  • It helps us manage the Fragment jump animation and unstack, and lets us use the Fragment as an Activity
    • Provide good technical support for single Activity and multi-fragment app

About single Activity and multiple fragments

Have you ever heard of a single Activity with multiple fragments? Instagram, Twitter and other popular apps have adopted this approach? What are the benefits? After in-depth understanding of the Android system architecture, THE author has his own views on this question

  • When attaching, the Window will be created and a ViewRootImpl will be created. There is a Surface in the ViewRootImpl. After the Surface is initialized, The SurfaceFlinger process corresponds to one Layer, that is, the application with multiple activities will have multiple layers. When VSYNC is emitted, SF will be responsible for Layer sorting and compositing. When there are too many layers, SF Compose will increase the burden of SF Compose to some extent. Therefore, when the Layer is small, the smoothness of our App can be increased to a certain extent
  • In addition, every Activity needs to communicate with SystemService across processes when it is started, but Fragment does not. In theory, it can reduce the time of page hopping

We know that the first problem we face when using fragments like activities is managing their backlogs, and the Navigation component helps us with this

So let’s go back to a navigation and onBackPressed and see how does the navigation control the Fragment start and exit

Initialization

<fragment
    android:id="@+id/my_nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/mobile_navigation" />
Copy the code

To use Navigation, add a tag to the layout of the main Activity that specifies that the current Activity has a NavHostFragment inside it and its Navigation scope is mobile_navigation. How is NavHostFragment initialized

A. The onCreate

public class NavHostFragment extends Fragment implements NavHost { 
    
    private NavController mNavController;

    @NonNull
    @Override
    public NavController getNavController() {
        if (mNavController == null) {
            throw new IllegalStateException("NavController is not available before onCreate()");
        }
        returnmNavController; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext(); MNavController = new NavHostController(context); / / 2. The registered lifecycle callback mNavController. SetLifecycleOwner (this); / / 3. Registered return event dispenser mNavController. SetOnBackPressedDispatcher (requireActivity () getOnBackPressedDispatcher ()); // 4. Create a Fragment navigator and add it to NavController to manage onCreateNavController(mNavController); .if(navState ! = null) { // Navigation controller state overrides arguments mNavController.restoreState(navState); }else{ final Bundle args = getArguments(); ResId Final int graphId = args! = null ? args.getInt(KEY_GRAPH_ID) : 0;if(graphId ! = 0) { mNavController.setGraph(graphId); }else{ mNavController.setMetadataGraph(); }}} protected void onCreateNavController(@nonnull NavController NavController) {// Add DialogFragment and the Fragment navigator navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); }}Copy the code

As can be seen from the inheritance relationship of NavHostFragment, it implements NavHost interface, that is to say, it holds NavController internally. Its onCreate method is mainly used to build and initialize NavController

  • Create navigation controller NavHostController, which is a wrapper class for NavController
  • Register life cycle callbacks
  • Register return event dispatcher
  • Callback onCreateNavController to inject the Fragment/DialogFragment navigator into NavController
  • Inject a navigation diagram for the NavController

We will focus on the instantiation of the NavController first, but we will discuss the implementation of the navigator function and the distribution of return events later

Instantiation of NavController

Public class NavController {// Final Deque<NavDestination> mBackStack = new ArrayDeque<>(); Private Final SimpleNavigatorProvider mNavigatorProvider = newSimpleNavigatorProvider() {
        @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) {// Clear the listener for navigatorif(previousNavigator ! = null) { previousNavigator.removeOnNavigatorNavigatedListener(mOnNavigatedListener); } / / add new Navigator listeners Navigator. AddOnNavigatorNavigatedListener (mOnNavigatedListener); }returnpreviousNavigator; }}; public NavController(@NonNull Context context) { mContext = context;while (context instanceof ContextWrapper) {
            if (context instanceof Activity) {
                mActivity = (Activity) context;
                break; } context = ((ContextWrapper) context).getBaseContext(); } / / add a NavGraphNavigator mNavigatorProvider. AddNavigator (new NavGraphNavigator (mContext)); / / add a ActivityNavigator mNavigatorProvider. AddNavigator (new ActivityNavigator (mContext)); }}Copy the code

The NavController is named the navigation controller, as it is, and internally holds a NavigatorProvider for caching and providing the navigator

As you can see in its constructor, it adds two navigators, NavGraphNavigator and ActivityNavigator, respectively. In addition to FragmentNavigator, there are three navigators. They are responsible for the implementation of navigation for specific business scenarios, which we will analyze as we use them

2) review

Here you can see the structure design of its Navigation is more clear

  • NavController: Provides interfaces for the upper layer, navigation services and stack maintenance
  • NavigationProvider: Maintains the navigator
  • Navigator: a Navigator that handles specific jumps and exits
    • ActivityNavigator
    • FragmentNavigator
    • GraphNavigator

Now that you understand the design structure, look at the specific navigate operation

Two. Navigation operation

public class NavController { 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); Bundle combinedArgs = null;if(navAction ! = null) {// 2.1 Reads navigation configuration options from NavActionif(navOptions == null) { navOptions = navAction.getNavOptions(); } / / 2.2 for destination ID destId = navAction getDestinationId (); / / 2.3 into the general parameters of the Bundle navActionArgs = navAction. GetDefaultArguments ();if (navActionArgs != null) {
                combinedArgs = new Bundle();
                combinedArgs.putAll(navActionArgs);
            }
        }

        if(args ! = null) {if(combinedArgs == null) { combinedArgs = new Bundle(); } combinedArgs.putAll(args); } // 3. If only the ID of popUp exists, the stack operation is performedif(destId == 0 && navOptions ! = null && navOptions.getPopUpTo() ! = 1) {/ / perform a stack logic popBackStack (navOptions. GetPopUpTo (), navOptions. IsPopUpToInclusive ());return;
        }
        
        if (destId == 0) {
            throw new IllegalArgumentException("Destination id == 0 can only be used"
                    + " in conjunction with a valid navOptions.popUpTo"); NavDestination node = findDestination(destId);if (node == null) {
            final String dest = NavDestination.getDisplayName(mContext, destId);
            throw new IllegalArgumentException("navigation destination "+ dest + (navAction ! = null ?" referenced from action " + NavDestination.getDisplayName(mContext, resId)
                    : "")
                    + " is unknown to this NavController"); } // navigate(node, combinedArgs, navOptions, navigatorExtras); }}Copy the code

The details of a navigation process are as follows

  • Gets the element at the top of the stack
  • Get the NavAction object corresponding to the ID, describing the navigation action
    • NavAction is the tag element information that we define in the XML of the navigation diagram
  • If only the PopUpId exists, the popBackStack is called to remove the stack
  • If destId is present, the overloaded navigate method is called to perform the push operation

Now let’s look at the push operation

A) into the stack

public class NavController { private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { ...... / / 1. According to the Name of the node to obtain the corresponding Navigator Navigator < NavDestination > the Navigator. = mNavigatorProvider getNavigator ( node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); Navigator. navigate(node, finalArgs, navOptions, navigatorExtras); // 3. Navigation succeeded, update stack elementif(newDest ! = null) { ...... // 3.1 Add an element of type mGraph to the fallback stack, keeping it at the bottomif(mBackStack.isEmpty()) { mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel)); ArrayDeque<NavBackStackEntry> Hierarchy = new ArrayDeque<>(); NavDestination destination = newDest;while(destination ! = null && findDestination(destination.getId()) == null) { NavGraph parent = destination.getParent();if(parent ! = null) { hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel)); } destination = parent; } mBackStack.addAll(hierarchy); NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, newDest.addInDefaultArgs(finalArgs), mViewModel); mBackStack.add(newBackStackEntry); } updateOnBackPressedCallbackEnabled();if(popped || newDest ! = null) { dispatchOnDestinationChanged(); }}}Copy the code

The operation of pushing the stack is relatively clear, mainly consisting of the following three steps

  • Get the corresponding Navigator based on the destination’s information
  • Call navigator. navigate to the appropriate page
  • If newDest exists, the navigation operation is of the Fragment type and is added to the mBackStack for maintenance

The ActivityNavigator implementation class returns null after navigation. Since the Activity stack is maintained by AMS, we really don’t need to manually maintain it again, so here we select FragmentNavigator to see its implementation

FragmentNavigator push operation

public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> { private final Context mContext; private final FragmentManager mFragmentManager; private final int mContainerId; private ArrayDeque<Integer> mBackStack = new ArrayDeque<>(); public FragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) { mContext = context; mFragmentManager = manager; // Id of the container mContainerId = containerId; } @Override public void navigate(@NonNull Destination destination, @Nullable Bundle args, @ Nullable NavOptions NavOptions) {/ / to create fragments instance object final fragments frag = destination. CreateFragment (args); / / build the Transaction final FragmentTransaction ft. = mFragmentManager beginTransaction (); Int enterAnim = navOptions! = null ? navOptions.getEnterAnim() : -1; intexitAnim = 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); } ft.replace(mContainerId, frag); final @IdRes int destId = destination.getId(); final boolean initialNavigation = mBackStack.isEmpty(); final boolean isClearTask = navOptions ! = null && navOptions.shouldClearTask(); Final Boolean isSingleTopReplacement = navOptions! = null && ! initialNavigation && navOptions.shouldLaunchSingleTop() && mBackStack.peekLast() == destId; int backStackEffect;if (initialNavigation || isClearTask) {
            backStackEffect = BACK_STACK_DESTINATION_ADDED;
        } else if(isSingleTopReplacement) {// Handle SingleTopif (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();
                ft.addToBackStack(getBackStackName(destId));
                mPendingBackStackOperations++;
            }
            backStackEffect = BACK_STACK_UNCHANGED;
        } else {
            ft.addToBackStack(getBackStackName(destId));
            mPendingBackStackOperations++;
            backStackEffect = BACK_STACK_DESTINATION_ADDED;
        }
        ft.setReorderingAllowed(true);
        ft.commit();
        // The commit succeeded, update our view of the world
        if(backStackEffect == BACK_STACK_DESTINATION_ADDED) { mBackStack.add(destId); } dispatchOnNavigatorNavigated(destId, backStackEffect); }}Copy the code

The FragmentNavigator structure we saw above when we started NavController. It passes in the childFragmentManager of NavHostFragment. Its navigate operation is the replace operation on FragmentTransaction

2) out of the stack

public class NavController {

    public boolean popBackStack(@IdRes int destinationId, boolean inclusive) {
        boolean popped = popBackStackInternal(destinationId, inclusive);
        // Only return true if the pop succeeded and we've dispatched // the change to a new destination return popped && dispatchOnDestinationChanged(); } boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) { if (mBackStack.isEmpty()) { // Nothing to pop if the back stack is empty return false; } ArrayList
      
        popOperations = new ArrayList<>(); / / 1. Traverse back stack, record to the stack elements of the Navigator Iterator < NavBackStackEntry > Iterator. = mBackStack descendingIterator (); boolean foundDestination = false; while (iterator.hasNext()) { NavDestination destination = iterator.next().getDestination(); / / to get to the stack elements corresponding to the Navigator. The Navigator Navigator. = mNavigatorProvider getNavigator (destination. GetNavigatorName ()); / / 1.1 records to the stack of elements of the Navigator. The if (inclusive | | destination. The getId ()! = destinationId) { popOperations.add(navigator); } // Terminate if (destination.getid () == destinationId) {foundDestination = true; break; }} if (! foundDestination) { ...... return false; } // 2. Use the Navigator to popBackStack the popBackStack method Boolean popped = false; for (Navigator navigator : PopOperations) {// 2.1 Call navigator's popBackStack if (navigator.popbackstack ()) {// Remove from mBackStack NavBackStackEntry entry = mBackStack.removeLast(); . popped = true; } else { // The pop did not complete successfully, so stop immediately break; } } updateOnBackPressedCallbackEnabled(); return popped; }}
      Copy the code

PopUp is similar to an Activity singleTask. If it exists before, it clears all the pages at the top of the stack

  • Iterate through the NavController rollback, recording the Navigator to popOperations for the element to be pushed
  • Call navigator. popBackStack to remove the stack & remove the last element of mBackStack

Here we also look at the implementation of FragmentNavigator

FragmentNavigator unstack operation

public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {

   @Override
    public boolean popBackStack() {
        if (mBackStack.isEmpty()) {
            return false;
        }
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring popBackStack() call: FragmentManager has already"
                    + " saved its state");
            return false;
        }
        mFragmentManager.popBackStack(
                generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
                FragmentManager.POP_BACK_STACK_INCLUSIVE);
        mBackStack.removeLast();
        return true; }}Copy the code

FragmentNavigator out operation than the stack operation more concise, direct call the mFragmentManager. PopBackStack remove target element

3) review

The details of a navigation process are as follows

  • Gets the element at the top of the stack
  • Get the NavAction object corresponding to the ID, describing the navigation action
    • NavAction is the tag element information that we define in the XML of the navigation diagram
  • If only the PopUpId exists, the popBackStack is invokedThe stack,
    • Iterate through the NavController rollback, recording the Navigator to popOperations for the element to be pushed
    • Call navigator. popBackStack to remove the stack & remove the last element of mBackStack
      • FragmentNavigator directly call the mFragmentManager. PopBackStack removes the target element
  • If destId is present, the overloaded navigate method is calledInto the stack,
    • Get the corresponding Navigator based on the destination’s information
    • Call navigator. navigate to the appropriate page
      • The FragmentNavigator’s navigate operation is the replace operation on the FragmentTransaction
    • If newDest exists, the navigation operation is of the Fragment type and is added to the mBackStack for maintenance

Now that the Navigation component is on and off the stack, how does Navigation distribute return events

Return event distribution

During initialization we see that the onBackPressed listener is registered in the NavHostFragment

public class NavHostFragment extends Fragment implements NavHost { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); . / / 1. RequireActivity (.) getOnBackPressedDispatcher () to obtain the dispenser / / 2. To a mNavController register callback mNavController. SetOnBackPressedDispatcher (requireActivity () getOnBackPressedDispatcher ()); . } } public class NavController { private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) {
        @Override
        public void handleOnBackPressed() { popBackStack(); }}; voidsetOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) { ...... // It registers a callback dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback) for the Activity's mBackPressedCallback; }}Copy the code

When the NavController initializes in the NavHostFragment, it requests the Activity’s return event dispatcher, and the NavController registers a callback to respond to the dispatcher

Let’s first look at how OnBackPressedDispatcher adds a callback

Add listener callback

public final class OnBackPressedDispatcher {

    @MainThread
    public void addCallback(@NonNull LifecycleOwner owner,
            @NonNull OnBackPressedCallback onBackPressedCallback) {
        Lifecycle lifecycle = owner.getLifecycle();
        if (lifecycle.getCurrentState() == Lifecycle.State.DESTROYED) {
            return; } / / for onBackPressedCallback addCancellable onBackPressedCallback. AddCancellable (new LifecycleOnBackPressedCancellable(lifecycle, onBackPressedCallback)); }}Copy the code

Here the implementation of the more interesting, it does not directly to our incoming onBackPressedCallback added to a callback queue, but created LifecycleOnBackPressedCancellable first, Then call the onBackPressedCallback addCancellable method

Let’s look at their implementations separately

1. LifecycleOnBackPressedCancellable created

public final class OnBackPressedDispatcher { 
    
    private class LifecycleOnBackPressedCancellable implements LifecycleEventObserver,
            Cancellable {
        private final Lifecycle mLifecycle;
        private final OnBackPressedCallback mOnBackPressedCallback;

        @Nullable
        private Cancellable mCurrentCancellable;

        LifecycleOnBackPressedCancellable(@NonNull Lifecycle lifecycle,
                @NonNull OnBackPressedCallback onBackPressedCallback) {
            mLifecycle = lifecycle;
            mOnBackPressedCallback = onBackPressedCallback;
            lifecycle.addObserver(this);
        }

        @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if(event == Lifecycle.Event.ON_START) { // 1. When onStart is called back, Call addCancellableCallback of the external class to inject OnBackPressedCallback into the mOnBackPressedCallbacks queue mCurrentCancellable = addCancellableCallback(mOnBackPressedCallback); }else if (event == Lifecycle.Event.ON_STOP) {
                // Should always be non-null
                if (mCurrentCancellable != null) {
                    mCurrentCancellable.cancel();
                }
            } else if(event == Lifecycle.event.on_destroy) {// automatically remove cancel() from the mOnBackPressedCallbacks queue when onDestroy is removed; } } @Override public voidcancel() {
            mLifecycle.removeObserver(this);
            mOnBackPressedCallback.removeCancellable(this);
            if(mCurrentCancellable ! = null) { mCurrentCancellable.cancel(); mCurrentCancellable = null; } } } final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>(); private class OnBackPressedCancellable implements Cancellable { private final OnBackPressedCallback mOnBackPressedCallback; OnBackPressedCancellable(OnBackPressedCallback onBackPressedCallback) { mOnBackPressedCallback = onBackPressedCallback; } @Override public voidcancel() {/ / remove the OnBackPressedCallback mOnBackPressedCallbacks. Remove (mOnBackPressedCallback); / / remove Cancelable mOnBackPressedCallback. RemoveCancellable (this); }} Cancellable addCancellableCallback(@nonnull OnBackPressedCallback OnBackPressedCallback) {// 1.1 will OnBackPressedCallback added to the queue of OnBackPressedDispatcher mOnBackPressedCallbacks. Add (onBackPressedCallback); // 1.2 created an OnBackPressedCancellable to add to the onBackPressedCallback cache // because of the mOnBackPressedCallbacks pair OnBackPressedCallback is not visible, So the purpose of this Cancelable is to help OnBackPressedCancellable remove OnBackPressedCancellable cancellable = from mOnBackPressedCallbacks new OnBackPressedCancellable(onBackPressedCallback); onBackPressedCallback.addCancellable(cancellable);returncancellable; }}Copy the code

LifecycleOnBackPressedCancellable is OnBackPressedDispatcher inner classes

  • OnBackPressedCallback is posted to the external class mOnBackPressedCallbacks for subsequent distribution only when the onStart callback is called
  • The OnBackPressedCallback is removed from the mOnBackPressedCallbacks when the onDestroy callback is called

In this way, it is possible to automate the addition and release of OnBackPressedCallback callbacks following the lifecycle

2. Add Cancelable to onBackPressedCallback

public abstract class OnBackPressedCallback { private boolean mEnabled; private CopyOnWriteArrayList<Cancellable> mCancellables = new CopyOnWriteArrayList<>(); void addCancellable(@NonNull Cancellable cancellable) { mCancellables.add(cancellable); }}Copy the code

Let’s look at how the Activity to return event distribution to NavController. MOnBackPressedCallback

Return to the event distribution process

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
        LifecycleOwner,
        ViewModelStoreOwner,
        SavedStateRegistryOwner,
        OnBackPressedDispatcherOwner {

    private final OnBackPressedDispatcher mOnBackPressedDispatcher =
            new OnBackPressedDispatcher(new Runnable() {
                @Override
                public void run() { ComponentActivity.super.onBackPressed(); }}); @Override @MainThread public voidonBackPressed() {
        mOnBackPressedDispatcher.onBackPressed();
    }

    @NonNull
    @Override
    public final OnBackPressedDispatcher getOnBackPressedDispatcher() {
        returnmOnBackPressedDispatcher; }}Copy the code

Return events sent by OnBackPressedDispatcher are supported in ComponentActivity, The implementation agent in onBackPressed gives the onBackPressed method of OnBackPressedDispatcher

public final class OnBackPressedDispatcher {

    @Nullable
    private final Runnable mFallbackOnBackPressed;

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final ArrayDeque<OnBackPressedCallback> mOnBackPressedCallbacks = new ArrayDeque<>();

    public OnBackPressedDispatcher() { this(null); } public OnBackPressedDispatcher(@nullable Runnable fallbackOnBackPressed) { It's the final implementation of the Activity's onBackPressed. MFallbackOnBackPressed = fallbackOnBackPressed; } @MainThread public voidonBackPressed() {// 1. Iterator<OnBackPressedCallback> Iterator = mOnBackPressedCallbacks.descendingIterator();while(iterator.hasNext()) { OnBackPressedCallback callback = iterator.next(); // 2. Distribute this event to the first oneenableThe OnBackPressedCallbackif (callback.isEnabled()) {
                callback.handleOnBackPressed();
                return; }} // 3. If it does not exist, the Activity's onBackPressed is handledif(mFallbackOnBackPressed ! = null) { mFallbackOnBackPressed.run(); }}... }Copy the code

The distribution process is clearer than the listener addition process, and the main steps are as follows

  • Backward traverse listens for the callback queue mOnBackPressedCallbacks
  • Distribute this event to the first enable OnBackPressedCallback
  • If not, the Activity’s onBackPressed handles it

The handleOnBackPressed implementation of OnBackPressedCallback, which we’ve already seen in NavController, calls popBackStack() to perform an out-of-stack operation that won’t be described here

3) review

The distribution of navigational events is supported by OnBackPressedDispatcher in ComponentActivity

  • Registration of listener callbacks
    • OnBackPressedCallback describe an event monitoring, it will be injected into the OnBackPressedDispatcher. MOnBackPressedCallbacks queue
    • Cancelable help onBackPressedCallback it from OnBackPressedDispatcher. MOnBackPressedCallbacks removed
  • Distribution of events
    • Backward traverse listens for the callback queue mOnBackPressedCallbacks
    • Distribute this event to the first enable OnBackPressedCallback
    • If not, the Activity’s onBackPressed handles it
  • Event handling
    • Implemented in NavController, which calls popBackStack to perform an off-stack operation

conclusion

Navigation initialization

The functions of each component of Navigation are as follows

  • NavController: Provides interfaces for the upper layer, navigation services and stack maintenance
  • NavigationProvider: Maintains the navigator
  • Navigator: a Navigator that handles specific jumps and exits
    • ActivityNavigator
    • FragmentNavigator
    • GraphNavigator

Navigation on and off the stack

The details of a navigation process are as follows

  • Gets the element at the top of the stack
  • Get the NavAction object corresponding to the ID, describing the navigation action
    • NavAction is the tag element information that we define in the XML of the navigation diagram
  • If only the PopUpId exists, the popBackStack is invokedThe stack,
    • Iterate through the NavController rollback, recording the Navigator to popOperations for the element to be pushed
    • Call navigator. popBackStack to remove the stack & remove the last element of mBackStack
      • FragmentNavigator directly call the mFragmentManager. PopBackStack removes the target element
  • If destId is present, the overloaded navigate method is calledInto the stack,
    • Get the corresponding Navigator based on the destination’s information
    • Call navigator. navigate to the appropriate page
      • The FragmentNavigator’s navigate operation is the replace operation on the FragmentTransaction
    • If newDest exists, the navigation operation is of the Fragment type and is added to the mBackStack for maintenance

Returns the distribution of events

The distribution of navigational events is supported by OnBackPressedDispatcher in ComponentActivity

  • Registration of listener callbacks
    • OnBackPressedCallback describe an event monitoring, it will be injected into the OnBackPressedDispatcher. MOnBackPressedCallbacks queue
    • Cancelable help onBackPressedCallback it from OnBackPressedDispatcher. MOnBackPressedCallbacks removed
  • Distribution of events
    • Backward traverse listens for the callback queue mOnBackPressedCallbacks
    • Distribute this event to the first enable OnBackPressedCallback
    • If not, the Activity’s onBackPressed handles it
  • Event handling
    • Implemented in NavController, which calls popBackStack to perform an off-stack operation

conclusion

Although Navigation component was contacted relatively early, there was no proper opportunity for me to make efforts to look at it. This time, I read its source code thoroughly, which can be regarded as completing my P2 task

The overall harvest is not small, at least in the selection of technical architecture is another choice, if used, it is more convenient for the subsequent customization and expansion of Navigation