SoFunction
Updated on 2025-04-05

Analysis and solution of Android Navigation reconstruction Fragment problem

Preface

Recently, BottomNavigationView combined with Navigation has been used in the project to realize the bottom navigation bar switching page business.

(bottomNavigationView, navController);

It turned out that the Fragment was rebuilt every time I clicked the bottom navigation bar to switch, so I analyzed the source code and studied the solution.

Source code analysis:

First look at the layout file:

<?xml version="1.0" encoding="utf-8"?>
< xmlns:andro
    xmlns:app="/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:
        android:name=""
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/bottom_nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph" />

    <
        android:
        android:layout_width="0dp"
        android:layout_height="50dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_menu" />
</>

After calling (BottomNavigationView, NavController), the setupWithNavController method actually listens for the navigation bar selection event by calling the BottomNavigationView#setOnNavigationItemSelectedListener method.
During the listening process, the NavController#navigate method will eventually be called to enter the Navigation source code.

Navigation source code analysis

First, let’s look at the execution process of NavHostFragment.

1. NavHostFragment#onInflate

Because fragment is declared in xml, the onInflate method of Fragment is called first.

    @CallSuper
    @Override
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
            @Nullable Bundle savedInstanceState) {
        (context, attrs, savedInstanceState);

        final TypedArray navHost = (attrs,
                );
        final int graphId = (
                .NavHost_navGraph, 0);
        if (graphId != 0) {
            mGraphId = graphId;
        }
        ();

        final TypedArray a = (attrs, );
        final boolean defaultHost = (.NavHostFragment_defaultNavHost, false);
        if (defaultHost) {
            mDefaultNavHost = true;
        }
        ();
    }

The onInflate method mainly parses the navGraph attribute and defaultNavHost attribute values ​​from XML attributes.

2. NavHostFragment#onAttach

According to the Fragment lifecycle, then the onAttach method is executed.

    @CallSuper
    @Override
    public void onAttach(@NonNull Context context) {
        (context);
        if (mDefaultNavHost) {
            getParentFragmentManager().beginTransaction()
                    .setPrimaryNavigationFragment(this)
                    .commit();
        }
    }

The onAttach method mainly sets NavHostFragment as the main navigation container of the navigator.

3. NavHostFragment#onCreate

 	@CallSuper
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        final Context context = requireContext();

		// 1. Instantiate NavHostController        mNavController = new NavHostController(context);
        (this);
        (requireActivity().getOnBackPressedDispatcher());
        (
                mIsPrimaryBeforeOnCreate != null &amp;&amp; mIsPrimaryBeforeOnCreate);
        mIsPrimaryBeforeOnCreate = null;
        (getViewModelStore());
        // 2. Create DialogFragmentNavigator and FragmentNavigator and add examples to NavigatorProvider        onCreateNavController(mNavController);

        Bundle navState = null;
        if (savedInstanceState != null) {
            navState = (KEY_NAV_CONTROLLER_STATE);
            if ((KEY_DEFAULT_NAV_HOST, false)) {
                mDefaultNavHost = true;
                getParentFragmentManager().beginTransaction()
                        .setPrimaryNavigationFragment(this)
                        .commit();
            }
            mGraphId = (KEY_GRAPH_ID);
        }
        if (navState != null) {
            (navState);
        }
        if (mGraphId != 0) {
        	// 3. Set navigation configuration file            (mGraphId);
        } else {
            final Bundle args = getArguments();
            final int graphId = args != null ? (KEY_GRAPH_ID) : 0;
            final Bundle startDestinationArgs = args != null
                    ? (KEY_START_DESTINATION_ARGS)
                    : null;
            if (graphId != 0) {
                (graphId, startDestinationArgs);
            }
        }
        (savedInstanceState);
    }

There are three main things to do in the onCreate method:

  • Instantiate the NavHostController object
  • Create DialogFragmentNavigator and FragmentNavigator and add to the member variable of NavigatorProvider type of NavigatorProvider of parent class NavController
  • Call NavHostController#setGraph method to set navigation configuration file nav_graph
public class NavHostController extends NavController {

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

Mainly look at the parent class initialization method:

public class NavController {
	private NavigatorProvider mNavigatorProvider = new NavigatorProvider();
	public NavController(@NonNull Context context) {
       	...
        (new NavGraphNavigator(mNavigatorProvider));
        (new ActivityNavigator(mContext));
    }
}

Mainly, it creates NavGraphNavigator and ActivityNavigator instances and adds them to the member variable mNavigatorProvider of NavController.

4. NavHostFragment#onCreateNavController

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

The onCreateNavController method is called in the onCreateNavController method to add DialogFragmentNavigator and FragmentNavigator examples.

5. NavigatorProvider

public class NavigatorProvider {
    private static final HashMap&lt;Class&lt;?&gt;, String&gt; sAnnotationNames = new HashMap&lt;&gt;();
	@NonNull
    static String getNameForNavigator(@NonNull Class&lt;? extends Navigator&gt; navigatorClass) {
        String name = (navigatorClass);
        if (name == null) {
        	// Customize the annotation of Navigator class             annotation = ();
            name = annotation != null ? () : null;
            ...
            (navigatorClass, name);
        }
        return name;
    }
	private final HashMap&lt;String, Navigator&lt;? extends NavDestination&gt;&gt; mNavigators = new HashMap&lt;&gt;()
	@Nullable
    public final Navigator&lt;? extends NavDestination&gt; addNavigator(
            @NonNull Navigator&lt;? extends NavDestination&gt; navigator) {
        String name = getNameForNavigator(());
        return addNavigator(name, navigator);
    }
    @CallSuper
    @Nullable
    public Navigator&lt;? extends NavDestination&gt; addNavigator(@NonNull String name,
            @NonNull Navigator&lt;? extends NavDestination&gt; navigator) {
        return (name, navigator);
    }
}

The NavigatorProvider class mainly stores the name specified when the key value is the custom Navigator annotation, and the value is the corresponding Navigator example.

Therefore, after the onCreate method is executed, the value of mNavigators in NavigatorProvider is:

("navigation", NavGraphNavigator)
("activity", ActivityNavigator)
("dialog", DialogFragmentNavigator)
("fragment", FragmentNavigator)

6. NavController#setGraph

@CallSuper
public void setGraph(@NavigationRes int graphResId) {
    setGraph(graphResId, null);
}

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

@CallSuper
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
    if (mGraph != null) {
        popBackStackInternal((), true);
    }
    mGraph = graph;
    onGraphCreated(startDestinationArgs);
}

@NonNull
public NavInflater getNavInflater() {
    if (mInflater == null) {
        mInflater = new NavInflater(mContext, mNavigatorProvider);
    }
    return mInflater;
}

In this method, the first thing is to instantiate NavInflater and call NavInflater#inflate to parse the navigation configuration file. The parsed structure is stored in the NavGraph class. NavGraph is a tree structure of NavDestination nodes that can be obtained by ID.

7. NavInflater#inflate

public final class NavInflater {
	private Context mContext;
	private NavigatorProvider mNavigatorProvider;
    public NavInflater(@NonNull Context context, @NonNull NavigatorProvider navigatorProvider) {
        mContext = context;
        mNavigatorProvider = navigatorProvider;
    }
	    @NonNull
    public NavGraph inflate(@NavigationRes int graphResId) {
        ...
        NavDestination destination = inflate(res, parser, attrs, graphResId);
        ...
    }
	 @NonNull
    private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
            @NonNull AttributeSet attrs, int graphResId)
            throws XmlPullParserException, IOException {
        Navigator<?> navigator = (());
        final NavDestination dest = ();
        (mContext, attrs);
        final int innerDepth = () + 1;
        int type;
        int depth;
        while ((type = ()) != XmlPullParser.END_DOCUMENT
                && ((depth = ()) >= innerDepth
                || type != XmlPullParser.END_TAG)) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            if (depth > innerDepth) {
                continue;
            }

            final String name = ();
            if (TAG_ARGUMENT.equals(name)) {
                inflateArgumentForDestination(res, dest, attrs, graphResId);
            } else if (TAG_DEEP_LINK.equals(name)) {
                inflateDeepLink(res, dest, attrs);
            } else if (TAG_ACTION.equals(name)) {
                inflateAction(res, dest, attrs, parser, graphResId);
            } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
                final TypedArray a = (
                        attrs, );
                final int id = (
                        .NavInclude_graph, 0);
                ((NavGraph) dest).addDestination(inflate(id));
                ();
            } else if (dest instanceof NavGraph) {
                ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
            }
        }
        return dest;
    }
	...
}

NavInflater's main job is to parse navigation configuration files. Next, let’s look back at the onGraphCreated method called in the setGraph method.

8. NavController#onGraphCreated

    private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
        ...
        if (mGraph != null && ()) {
            boolean deepLinked = !mDeepLinkHandled && mActivity != null
                    && handleDeepLink(());
            if (!deepLinked) {
                // Navigate to the first destination in the graph
                // if we haven't deep linked to a destination
                navigate(mGraph, startDestinationArgs, null, null);
            }
        } else {
            dispatchOnDestinationChanged();
        }
    }

At the beginning, the navigate method will be executed.

9. NavController#navigate

    private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable  navigatorExtras) {
        boolean popped = false;
        boolean launchSingleTop = false;
        if (navOptions != null) {
            if (() != -1) {
                popped = popBackStackInternal((),
                        ());
            }
        }
        Navigator<NavDestination> navigator = (
                ());
        Bundle finalArgs = (args);
        NavDestination newDest = (node, finalArgs,
                navOptions, navigatorExtras);
        ...
    }

According to the analysis, the Navigator obtained by getNavigator is a NavGraphNavigator instance.

10. NavGraphNavigator#navigate

    @Nullable
    @Override
    public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
        int startId = ();
        if (startId == 0) {
            throw new IllegalStateException("no start destination defined via"
                    + " app:startDestination for "
                    + ());
        }
        NavDestination startDestination = (startId, false);
        if (startDestination == null) {
            final String dest = ();
            throw new IllegalArgumentException("navigation destination " + dest
                    + " is not a direct child of this NavGraph");
        }
        Navigator<NavDestination> navigator = (
                ());
        return (startDestination, (args),
                navOptions, navigatorExtras);
    }

In the navigate method, find the NavDestination variable through startId, and then obtain the corresponding Navigator instance based on the name obtained by the NavDestination#getNavigatorName method. The FragmentNavigator instance is obtained here.

11. FragmentNavigator#navigate

    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable  navigatorExtras) {
        ...
        String className = ();
        if ((0) == '.') {
            className = () + className;
        }
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        (args);
        final FragmentTransaction ft = ();

        int enterAnim = navOptions != null ? () : -1;
        int exitAnim = navOptions != null ? () : -1;
        int popEnterAnim = navOptions != null ? () : -1;
        int popExitAnim = navOptions != null ? () : -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;
            (enterAnim, exitAnim, popEnterAnim, popExitAnim);
        }

        (mContainerId, frag);
        (frag);

        final @IdRes int destId = ();
        final boolean initialNavigation = ();
        // TODO Build first class singleTop behavior for fragments
        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                && ()
                && () == destId;

        boolean isAdded;
        if (initialNavigation) {
            isAdded = true;
        } else if (isSingleTopReplacement) {
            // Single Top means we only want one instance on the back stack
            if (() > 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
                (
                        generateBackStackName((), ()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
                (generateBackStackName((), destId));
            }
            isAdded = false;
        } else {
            (generateBackStackName(() + 1, destId));
            isAdded = true;
        }
        if (navigatorExtras instanceof Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (<View, String> sharedElement : ().entrySet()) {
                ((), ());
            }
        }
        (true);
        ();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            (destId);
            return destination;
        } else {
            return null;
        }
    }

The navigate method uses FragmentFactory (reflection) to create fragment instances. Finally, add fragment instance through the FragmentTransaction#replace method.

Let's look back at the life cycle onCreateView method of NavHostFragment.

11. NavHostFragment#onCreateView

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        FragmentContainerView containerView = new FragmentContainerView(());
        // When added via XML, this has no effect (since this FragmentContainerView 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
        (getContainerId());
        return containerView;
    }

The default display view of NavHostFragment is the FragmentContainerView.

12. NavHostFragment#onViewCreated

    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        (view, savedInstanceState);
        (view, mNavController);
        // When added programmatically, we need to set the NavController on the parent - .,
        // the View that has the ID matching this NavHostFragment.
        if (() != null) {
            mViewParent = (View) ();
            if (() == getId()) {
                (mViewParent, mNavController);
            }
        }
    }
    public static void setViewNavController(@NonNull View view,
            @Nullable NavController controller) {
        (.nav_controller_view_tag, controller);
    }
    private static NavController findViewNavController(@NonNull View view) {
        while (view != null) {
            NavController controller = getViewNavController(view);
            if (controller != null) {
                return controller;
            }
            ViewParent parent = ();
            view = parent instanceof View ? (View) parent : null;
        }
        return null;
    }
    private static NavController getViewNavController(@NonNull View view) {
        Object tag = (.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;
    }

Set NavController to the tag of the root view View of NavHostFragment. When Navigation#findNavController is called later, the tag will be found from the incoming view and all parent views until the NavController is found.

From the above analysis, we can see that every time we call the NavController#navigate method, a new Fragment will be regenerated and FragmentTransaction#replace will be added, so we will see the phenomenon of rebuilding the Fragment every time.

Solution

Custom Navigator override Navigator#navigate method.

@("customNavigator")
public class CustomNavigator extends FragmentNavigator {

    private Context context;
    private FragmentManager fragmentManager;
    private int containerId;

    public CustomNavigator(@NonNull Context context, @NonNull FragmentManager fragmentManager, int containerId) {
        super(context, fragmentManager, containerId);

         = context;
         = fragmentManager;
         = containerId;
    }
    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable  navigatorExtras) {
        FragmentTransaction ft = ();
        // Get the currently displayed fragment        Fragment fragment = ();
        if (fragment != null) {
            (fragment);
        }
        final String tag = (());
        fragment = (tag);
        if (fragment != null) {
            (fragment);
        } else {
            fragment = instantiateFragment(context, fragmentManager, (), args);
            (containerId, fragment, tag);
        }
        (fragment);
        (true);
        ();
        return destination;
    }
}

Then add it through the NavigatorProvider:

NavController navController = (this, .nav_host_fragment);
().addNavigator(new CustomNavigator(this, getSupportFragmentManager(), .nav_host_fragment));

This is the article about the analysis and solution of Android Navigation reconstruction Fragment problem. For more related Android Navigation content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!