Android Navigation profile

You can use Navigation to manage APP page jumps. Using Navigation to switch between fragments can make the code simple and intuitive. The Navigation component also supports Fragment, Activity, Navigation and subgraph, custom target, etc.

The use of Navigation

Based on using

  1. Add project component dependencies
 def nav_dep = "2.0.0"
  
 implementation "androidx.navigation:navigation-fragment:$nav_dep"
 implementation "androidx.navigation:navigation-ui:$nav_dep"
Copy the code
  1. Navigation_jetpack.xml in the “RES” folder of module, create a new navigation XML file under the navigation folder: navigation_jetpack.xml
<? xml version="1.0" encoding="utf-8"? > <navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@id/first_fragment">

    <fragment
            android:id="@+id/first_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.FirstNavigationFragment"
            android:label="first_fragment"
            tools:layout="@layout/fragment_first_navigation">
        <action
                android:id="@+id/action_to_second_fragment"
                app:destination="@id/second_fragment" />

    </fragment>

    <fragment
            android:id="@+id/second_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.SecondNavigationFragment"
            android:label="second_fragment"
            tools:layout="@layout/fragment_second_navigation">
        <action
                android:id="@+id/action_popup_to_first_fragment"
                app:popUpTo="@id/first_fragment" />
        <action
                android:id="@+id/action_to_third_fragment"
                app:destination="@id/third_fragment" />
        <argument
                android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
        />
    </fragment>

    <fragment
            android:id="@+id/third_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.ThirdNavigationFragment"
            android:label="third_navigation"
            tools:layout="@layout/fragment_third_navigation" >
        <action android:id="@+id/action_popup_to_first_fragment_from_third"
                app:popUpTo="@id/first_fragment"/>
    </fragment>
</navigation>
Copy the code

(1) The startDestination of navigation root node indicates the first fragment displayed. That is, the name attribute of the FirstNavigationFragment node indicates the fragment class it belongs to. The destination attribute of the action node in the fragment node is used to specify the next target fragment (4) The argument in the fragment node is used to transmit data. Represents the data passed to the current Fragment. The Key is the name attribute. The default is Android :defaultValue and the data type is argType. Create a Fragment. The ThirdNavigationFragment is used as an example. OnCreateView returns the layout View. OnViewCreated sets the click time and executes the corresponding action to complete the Fragment jump. When the page jumps, the onDestroyView method will be executed, and returning to the Fragment will execute the onCreateView method.

class ThirdNavigationFragment : Fragment(){ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle?) : View? {return LayoutInflater.from(this.activity).inflate(R.layout.fragment_third_navigation,container,false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        btn_third_fragment.setOnClickListener{
            Navigation.findNavController(it).navigate(R.id.action_popup_to_first_fragment_from_third)
        }
    }
}
Copy the code
  1. Create the Activity 4.1 Activity layout file.
<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"> <! <fragment Android :id="@+id/fragment_navigation"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:defaultNavHost="false"
            app:navGraph="@navigation/navigation_jetpack"/ > <! <FrameLayout android:id="@+id/ll_fragment_navigation"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
    ></FrameLayout>
</LinearLayout>
Copy the code

(1) When using the XML implementation or the code implementation, be sure to annotate either one. (2) If implemented using XML, fragment must set id. Navigation_jetpack.xml (3) name must be specified as the following value, This is switching fragments container android: name = “androidx. Navigation. Fragments. NavHostFragment” (4) whether defaultNavHost said interception return key, the default is false. If you are using a code to implement the layout file, use the following code in the Activity: (1) Initialize NavHostFragment. (2) Bind NavHostFragment to FrameLayout of the layout file.

class NavigationActivity : AppCompatActivity(){
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_navigation)

        val finalHost = NavHostFragment.create(R.navigation.navigation_jetpack)

        supportFragmentManager.beginTransaction()
            .replace(R.id.ll_fragment_navigation, finalHost)
            .setPrimaryNavigationFragment(finalHost)
            .commit()
    }
}
Copy the code
  1. Switch the fragments

There are two main ways to switch Fragment: (1) Method 1:

Navigation.findNavController(it).navigate(R.id.action_to_second_fragment)
Copy the code

(2) Mode 2:

NavHostFragment.findNavController(this).navigate(R.id.action_to_second_fragment,null)
Copy the code

Both implementations return a NavController class that controls the navigate of the page by calling the navigate method, meaning that NavController controls all Fragment navigation behavior. You can set the argument tag to set the type and default value of the argument received by the fragment.

 <argument
       android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
        />
Copy the code

Or use the traditional approach in your code:

 val bundle = Bundle()
 bundle.putString("name"."Blank")
 bundle.putInt("number",10)
 NavHostFragment.findNavController(this).navigate(R.id.action_to_second_fragment,bundle)
Copy the code

In the code, use the navigate() method and pass the Bundle to the target. In your receiving Fragment, use the getArguments() method to retrieve the package and use its contents. 8. Nested navigation diagrams can group destinations into subgraphs of the navigation diagram, also known as “nested diagrams”, and containing graphs are called “root diagrams”. We create a subgraph as follows: third_navigation.

<? xml version="1.0" encoding="utf-8"? > <navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@id/first_fragment">

    <fragment
            android:id="@+id/first_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.FirstNavigationFragment"
            android:label="first_fragment"
            tools:layout="@layout/fragment_first_navigation">
        <action
                android:id="@+id/action_to_second_fragment"
                app:destination="@id/second_fragment" />

    </fragment>

    <fragment
            android:id="@+id/second_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.SecondNavigationFragment"
            android:label="second_fragment"
            tools:layout="@layout/fragment_second_navigation">
        <action
                android:id="@+id/action_popup_to_first_fragment"
                app:popUpTo="@id/first_fragment" />
        <action
                android:id="@+id/action_to_third_fragment"
                app:destination="@id/third_navigation" />
        <argument
                android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
        />
    </fragment>

    <navigation
            android:id="@+id/third_navigation"
            app:startDestination="@id/third_fragment">
        <fragment
                android:id="@+id/third_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.ThirdNavigationFragment"
                android:label="third_navigation"
                tools:layout="@layout/fragment_third_navigation" >
            <action android:id="@+id/action_popup_to_first_fragment_from_third"
                    app:popUpTo="@id/first_fragment"/>
        </fragment>
    </navigation>
</navigation>
Copy the code
  1. Navigation_nested.xml layout file is as follows:
<? xml version="1.0" encoding="utf-8"? > <navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:id="@+id/navigation_nested"
            app:startDestination="@id/third_fragment">

    <fragment
            android:id="@+id/third_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.ThirdNavigationFragment"
            android:label="third_navigation"
            tools:layout="@layout/fragment_third_navigation" >
        <action android:id="@+id/action_popup_to_first_fragment_from_third"
                app:popUpTo="@id/first_fragment"/>
    </fragment>

</navigation>
Copy the code

Include navigation_nested in navigation_jetpack.

<? xml version="1.0" encoding="utf-8"? > <navigation xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            app:startDestination="@id/first_fragment">

    <fragment
            android:id="@+id/first_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.FirstNavigationFragment"
            android:label="first_fragment"
            tools:layout="@layout/fragment_first_navigation">
        <action
                android:id="@+id/action_to_second_fragment"
                app:destination="@id/second_fragment" />

    </fragment>

    <fragment
            android:id="@+id/second_fragment"
            android:name="com.jandroid.module_common.jetpack.navigation.SecondNavigationFragment"
            android:label="second_fragment"
            tools:layout="@layout/fragment_second_navigation">
        <action
                android:id="@+id/action_popup_to_first_fragment"
                app:popUpTo="@id/first_fragment" />
        <action
                android:id="@+id/action_to_third_fragment"
                app:destination="@id/navigation_nested" />
        <argument
                android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
        />
    </fragment>
    <include  app:graph="@navigation/navigation_nested"/>
</navigation>
Copy the code
  1. Other USES

Other uses of Navigation can be found in the Blog below, including Deep Link, etc.

Cloud.tencent.com/developer/a…

Blog.csdn.net/lyhhj/artic…

Cloud.tencent.com/developer/a…

Source code analysis

3.1 build NavController

NavHostFragment is a container. All Navigation operations are performed in the NavHostFragment, which is delegated to the NavController class. So let’s focus on how the NavController class is created and what the NavHostFragment class does during creation. NavHostFragment initialization can be implemented in two ways: 1. Configure XML files. 2: code implementation. Navhostfragment.create navHostFragment.create navHostFragment.create

  val finalHost = NavHostFragment.create(R.navigation.navigation_jetpack)

        supportFragmentManager.beginTransaction()
            .replace(R.id.ll_fragment_navigation, finalHost)
            .setPrimaryNavigationFragment(finalHost)
            .commit()
Copy the code
  1. The NavHostFragment.create method (1) initializes the Bundle and stores graphResId and startDestinationArgs in the Bundle. (2) Return NavHostFragment instance.
public static NavHostFragment create(@NavigationRes int graphResId,
            @Nullable Bundle startDestinationArgs) {
        Bundle b = null;
        if(graphResId ! = 0) { b = new Bundle(); b.putInt(KEY_GRAPH_ID, graphResId); }if(startDestinationArgs ! = null) {if (b == null) {
                b = new Bundle();
            }
            b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
        }

        final NavHostFragment result = new NavHostFragment();
        if(b ! = null) { result.setArguments(b); }return result;
    }
Copy the code
  1. NavHostFragment. OnInflate method when fragments, in the form of XML static load, the first is called onInflate method (called time: fragments of the associated Activity when performing the setContentView). (1) Mainly parse two attributes of the layout file. DefaultNavHost and navGraph, and initialize global variables (1) When defaltNavHost is true, NavHostFragment will be switched to the top of the fallback stack via FragmentManager, And it can intercept return key events (back events).
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
            @Nullable Bundle savedInstanceState) {
        super.onInflate(context, attrs, savedInstanceState);

        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
        final int graphId = a.getResourceId(R.styleable.NavHostFragment_navGraph, 0);
        final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);

        if(graphId ! = 0) { mGraphId = graphId; }if (defaultHost) {
            mDefaultNavHost = true;
        }
        a.recycle();
    }
Copy the code
  1. NavHostFragment. OnCreate method. Both XML and code implementations implement the Fragment onCreate method, which leads to the same result. The NavController is created here and there is a NavController object in the NavHostFragment. (1) Initialize NavController. NavController is the core class of navigation. (2) Save the FragmentNavigator class as key-value pairs in SimpleNavigatorProvider. This class will be described later. (4) Set graph to navController and build NavGraph. This module will be examined separately below. (5) When defaltNavHost is true, it will be set to the main navigation fragment. You can intercept return key events (back events). (6) Add FragmentNavigator via addNavigator, as analyzed below.
public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final Context context = requireContext();

        mNavController = new NavController(context);
        mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());

        Bundle navState = null;
        if(savedInstanceState ! = null) { navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                mDefaultNavHost = true; requireFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); }}if(navState ! = null) { // Navigation controller state overrides arguments mNavController.restoreState(navState); }if(mGraphId ! = 0) { // Set from onInflate() mNavController.setGraph(mGraphId); }else {
            // See if it was setby NavHostFragment.create() final Bundle args = getArguments(); final int graphId = args ! = null ? args.getInt(KEY_GRAPH_ID) : 0; final Bundle startDestinationArgs = args ! = null ? args.getBundle(KEY_START_DESTINATION_ARGS) : null;if(graphId ! = 0) { mNavController.setGraph(graphId, startDestinationArgs); }}}Copy the code
  1. NavController. OnCreateView method the NavHostFragment view only one FrameLayout layout, in NavHostFragment created, as it creates a FrameLayout as the carrier of the navigation interface.
 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        FrameLayout frameLayout = new FrameLayout(inflater.getContext());
        // When added via XML, this has no effect (since this FrameLayout is given the ID
        // automatically), but this ensures that the View exists as part of this Fragment's View // hierarchy in cases where the NavHostFragment is added programmatically as is required // for child fragment transactions frameLayout.setId(getId()); return frameLayout; }Copy the code
  1. NavController. OnViewCreated (1) when added by XML, the parent View is null, the root of our View is NavHostFragment. (2) But when added in code, you need to set the NavController on the parent.
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if(! (view instanceof ViewGroup)) { throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
        }
        // When added via XML, the parent is null and our view is the root of the NavHostFragment
        // but when added programmatically, we need to setthe NavController on the parent - i.e., // the View that has the ID matching this NavHostFragment. View rootView = view.getParent() ! = null ? (View) view.getParent() : view; Navigation.setViewNavController(rootView, mNavController); }Copy the code
  1. Navigation. SetViewNavController method. The main thing is to set the NavController object to the Tag of the rootView. It is convenient to recursively traverse the NavController object to ensure that the NavController object is unique.
  public static void setViewNavController(@NonNull View view,
            @Nullable NavController controller) {
        view.setTag(R.id.nav_controller_view_tag, controller);
    }
Copy the code

The relationship between NavController objects and NavHostFragments has been clarified. Let’s take a look at how NavController participates in navigation events.

3.2 get NavController

In order for the NavController to participate in the navigation event, it must obtain the object. There are two ways to do this when controlling navigation in fragments. (1) Navigation. FindNavController (it). Navigate (da ction_page R.i) (2) NavHostFragment. FindNavController (this) navigate (da ction R.i) In fact, either findNavController or findNavController returns NavController objects. When building the NavController object, we use the Navigation class, which is analyzed below. The findNavController method parameter is a View object, so the View is used to find the NavController. Remember the viewRoot used above? .

  1. The findNavController method doesn’t have any real code in it, as long as it calls the findViewNavController method.
   public static NavController findNavController(@NonNull View view) {
        NavController navController = findViewNavController(view);
        if (navController == null) {
            throw new IllegalStateException("View " + view + " does not have a NavController set");
        }
        return navController;
    }
Copy the code
  1. The findViewNavController method finds the NavController through a view recursive loop. The getViewNavController method is called internally.
 private static NavController findViewNavController(@NonNull View view) {
        while(view ! = null) { NavController controller = getViewNavController(view);if(controller ! = null) {return controller;
            }
            ViewParent parent = view.getParent();
            view = parent instanceof View ? (View) parent : null;
        }
        return null;
    }
Copy the code
  1. The getViewNavController method retrieves the NavController object by retrieving the view’s Tag, where the Tag ID and setViewNavController are both nav_controller_view_tag.
private static NavController getViewNavController(@NonNull View view) {
        Object tag = view.getTag(R.id.nav_controller_view_tag);
        NavController controller = null;
        if (tag instanceof WeakReference) {
            controller = ((WeakReference<NavController>) tag).get();
        } else if (tag instanceof NavController) {
            controller = (NavController) tag;
        }
        return controller;
    }
Copy the code

So far the NavController acquisition process has been analyzed.

4 Real navigation implementation

When implementing navigation, we need to generate NavGraph class according to navigation configuration file, and then according to each different action ID, find the corresponding NavDestination can realize the page navigation jump.

4.1 build NavGraph

  1. When the SimpleNavigatorProvider class builds NavController, it calls the following code in the onCreate method.
  mNavController = new NavController(context);
  mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
Copy the code

(1) mNavigatorProvider is a global variable in NavController, which internally saves Navigator class in the form of HashMap key-value pairs.

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); }returnpreviousNavigator; }};Copy the code

The createFragmentNavigator method builds the FragmentNavigator object, which contains the abstract class ActivityNavigator and the important implementation class NavGraphNavigator. This object of two classes is added to the Constructor of NavController.

public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> 
Copy the code

(3) The function of Navigator class is: it can instantiate the corresponding NavDestination, and can realize the navigation function, and has its own rollback stack. While building the NavController, we also call the navController.setgraph (graphId) method, which mainly builds the NavGraph. (1) Call getNavInflater to create NavInflater objects that parse navigation XML files

  public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
        setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
    }
Copy the code

(2) The Navinflater.inflate method builds a NavGraph based on the passed XML resource ID. The NavGraph forms the navigation map of the Fragment route, and navdestinations represent each destination of the navigation. After parsing the NavDestination, you need to require that the NavDestination be NavGraph, which is a subclass of NavDestination. And the NavDestination information is stored inside the NavGraph.

public NavGraph inflate(@NavigationRes int graphResId) { Resources res = mContext.getResources(); XmlResourceParser Parser = res.getxml (graphResId); final AttributeSet attrs = Xml.asAttributeSet(parser); try { String rootElement = parser.getName(); Public arinflate (public arinflate) {public arinflate (public arinflate); public arinflate (public arinflate) {public arinflate (public arinflate); public arinflate (public arinflate) {public arinflate (public arinflate); // Check the validityif(! (destination instanceof NavGraph)) { throw new IllegalArgumentException("Root element <" + rootElement + ">"
                        + " did not inflate into a NavGraph");
            }
            return (NavGraph) destination;
        } catch (Exception e) {
            throw new RuntimeException("Exception inflating "
                    + res.getResourceName(graphResId) + " line "+ parser.getLineNumber(), e); } finally { parser.close(); }}Copy the code

The inflate method above continues to be called internally. (1) The getNavigator method gets all the Navigator instances that were added during the NavController build, in this case the FragmentNavigator object. (2) createDestination method, which calls FragmentNavigator createDestination to build the Destination object. (3) onInflate method, called FragmentNavigator. Destination method for setting the fragments of the name of the class. (4) Inside the while loop, the navigation graph is constructed by recursion.

private NavDestination inflate(Resources res, XmlResourceParser parser, AttributeSet attrs)
        throws XmlPullParserException, IOException {
    Navigator navigator = mNavigatorProvider.getNavigator(parser.getName());
    final NavDestination dest = navigator.createDestination();
    dest.onInflate(mContext, attrs);
    final int innerDepth = parser.getDepth() + 1;
    int type;
    int depth;
    while ((type= parser.next()) ! = XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth ||type! = XmlPullParser.END_TAG)) {if (type! = XmlPullParser.START_TAG) {continue;
        }

        if (depth > innerDepth) {
            continue;
        }

        final String name = parser.getName();
        if(tag_argument.equals (name)) {// Parses parameters stored in dest inflateArgument(res, dest, attrs); }else if(tag_deep_link.equals (name)) {// Resolve deep links inflateDeepLink(res, dest, attrs); }else if(tag_action.equals (name)) {// Parse Action inflateAction(res, dest, attrs); }else if(tag_include.equals (name) &&dest instanceof NavGraph) {// If the child node is graph, load the destination of the child node. That is, through the include method. final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavInclude); final int id = a.getResourceId(R.styleable.NavInclude_graph, 0); ((NavGraph) dest).addDestination(inflate(id)); a.recycle(); }else if// Add destination ((dest instanceof NavGraph) {// Add destination ((dest instanceof NavGraph) to each NavGraph. dest).addDestination(inflate(res, parser, attrs)); }}return dest;
}
Copy the code
  1. OnGraphCreated method. After using the NavInflater class, the XML file is parsed to build the entire Graph. Back to the setGraph method, which is called after parsing and playing XML. (1) The popBackStackInternal method removes all old navigation charts from the stack. (2) Calling onGraphCreated mainly displays a navigation Fragment view.
 public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
        if(mGraph ! = null) { // Pop everything from the old graph off the back stack popBackStackInternal(mGraph.getId(),true);
        }
        mGraph = graph;
        onGraphCreated(startDestinationArgs);
    }
Copy the code
  1. The onGraphCreated method (1) restores the previous navigation state (2) calls the navigate method to display the first Fragment. Fragment of property app:startDestination in Navigation file. So they all end up with the navigate navigation method.
 private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
        if(mNavigatorStateToRestore ! = null) { ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList( KEY_NAVIGATOR_STATE_NAMES);if(navigatorNames ! = null) {for (String name : navigatorNames) {
                    Navigator navigator = mNavigatorProvider.getNavigator(name);
                    Bundle bundle = mNavigatorStateToRestore.getBundle(name);
                    if(bundle ! = null) { navigator.onRestoreState(bundle); }}}}if(mBackStackIdsToRestore ! = null) {for (int index = 0; index < mBackStackIdsToRestore.length; index++) {
                int destinationId = mBackStackIdsToRestore[index];
                Bundle args = (Bundle) mBackStackArgsToRestore[index];
                NavDestination node = findDestination(destinationId);
                if (node == null) {
                    throw new IllegalStateException("unknown destination during restore: "
                            + mContext.getResources().getResourceName(destinationId));
                }
                if(args ! = null) { args.setClassLoader(mContext.getClassLoader()); } mBackStack.add(new NavBackStackEntry(node, args)); } mBackStackIdsToRestore = null; mBackStackArgsToRestore = null; }if(mGraph ! = null && mBackStack.isEmpty()) { boolean deepLinked = mActivity ! = null && handleDeepLink(mActivity.getIntent());if(! deepLinked) { // Navigate to the first destinationin the graph
                // if we haven't deep linked to a destination navigate(mGraph, startDestinationArgs, null, null); }}}Copy the code

navigation

After building and getting the NavController object and the NavGraph. Here’s how to use it for real navigation. Begin the analysis with navigate. NavDestination is queried inside the Navigate method and navigated according to the different Navigator implementation.

  1. Navigate method (1) returns NavGraph if the callback is NULL, or the last item in the callback if not null. (2) Obtain the corresponding NavAction based on the ID. Then get the destination ID through NavAction. (4) Using the destination ID attribute, find the destination to be navigated through the findDestination method. (5) According to the name of the navigation destination, call getNavigator method to get the Navigator object. The counterpart here is FragmentNavigator.
 public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
            @Nullable Navigator.Extras navigatorExtras) {
        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) {if (navOptions == null) {
                navOptions = navAction.getNavOptions();
            }
            destId = navAction.getDestinationId();
            Bundle navActionArgs = navAction.getDefaultArguments();
            if (navActionArgs != null) {
                combinedArgs = new Bundle();
                combinedArgs.putAll(navActionArgs);
            }
        }

        if(destId == 0 && navOptions ! = null && navOptions.getPopUpTo() ! = -1) { popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());return;
        }
        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) {
        boolean popped = false;
        if(navOptions ! = null) {if(navOptions.getPopUpTo() ! = -1) { popped = popBackStackInternal(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()); } } Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( node.getNavigatorName()); Bundle finalArgs = node.addInDefaultArgs(args); NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras);Copy the code
  1. The implementation of FragmentNavigator follows this analysis to the FragmentNavigator subclass. Take a look at FragmentNavigator. Navigate method. (1) call instantiateFragment to build Fragment instances via reflection mechanism (2) handle animation logic such as inbound and outbound (3) Finally call FragmentManager to handle navigation logic. I guess the ActivityNavigator ends up calling the startActivity method too, so I won’t show you the code here.
 public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        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(); 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);
        ft.setPrimaryNavigationFragment(frag);
        ft.setReorderingAllowed(true); Ft.com MIT (); }Copy the code

summary

(1) The NavHostFragment is a navigation carrier that is referenced in the Activity layout file (or dynamically in the code) and holds the NavController class reference. (2) The NavController delegates navigation to the Navigator class, which has two important subclasses FragmentNavigator and ActivityNavigator. The NavController class holds NavInflater class references. (3) NavInflater is responsible for resolving the navnavigation file and building the NavGraph. (4) NavDestination contains Destination information. There is a Destination class inside FragmentNavigator and ActivityNavigator, which inherits NavDestination. (5) During page navigation, fragment operations are still handled by FragmentManager and Activity is executed by startActivity.

The following is a summary of the more complete class diagram information on the Internet: from the picture source