目录
- 前言
- Navigation源码分析
- 1. NavHostFragment#onInflate
- 2. NavHostFragment#onAttach
- 3. NavHostFragment#onCreate
- 4. NavHostFragment#onCreateNavController
- 5. NavigatorProvider
- 6. NavController#setGraph
- 7. NavInflater#inflate
- 8. NavController#onGraphCreated
- 9. NavController#navigate
- 10. NavGraphNavigator#navigate
- 11. FragmentNavigator#navigate
- 11. NavHostFragment#onCreateView
- 12. NavHostFragment#onViewCreated
- 解决方案
前言
最近项目中使用到了BottomNavigationView结合Navigation实现底部导航栏切换页面业务。
NavigationUI.setupWithNavController(bottomNavigationView, navController);
结果发现每次点击底部导航栏切换的时候都会重建Fragment,于是分析了源码,并研究了解决方案。
源码分析:
首先看布局文件:
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" 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" /> <com.google.android.material.bottomnavigation.BottomNavigationView android:id="@+id/bottom_nav_view" 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" /> </androidx.constraintlayout.widget.ConstraintLayout>
当调用NavigationUI.setupWithNavController(BottomNavigationView, NavController)以后,setupWithNavController方法内部其实通过调用BottomNavigationView#setOnNavigationItemSelectedListener方法监听导航栏选中事件。
在BottomNavigationView.OnNavigationItemSelectedListener监听中,最终会调用到NavController#navigate方法,进入Navigation源码中。
Navigation源码分析
首先看NavHostFragment的执行流程。
1. NavHostFragment#onInflate
因为在xml中声明fragment因此,首先调用Fragment的onInflate方法。
@CallSuper @Override public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs, @Nullable Bundle savedInstanceState) { super.onInflate(context, attrs, savedInstanceState); final TypedArray navHost = context.obtainStyledAttributes(attrs, androidx.navigation.R.styleable.NavHost); final int graphId = navHost.getResourceId( androidx.navigation.R.styleable.NavHost_navGraph, 0); if (graphId != 0) { mGraphId = graphId; } navHost.recycle(); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment); final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false); if (defaultHost) { mDefaultNavHost = true; } a.recycle(); }
onInflate方法中主要是从XML属性中解析navGraph属性和defaultNavHost属性值。
2. NavHostFragment#onAttach
根据Fragment生命周期,然后执行的是onAttach方法。
@CallSuper @Override public void onAttach(@NonNull Context context) { super.onAttach(context); if (mDefaultNavHost) { getParentFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); } }
onAttach方法中主要是设置NavHostFragment为导航器的主导航容器。
3. NavHostFragment#onCreate
@CallSuper @Override public void onCreate(@Nullable Bundle savedInstanceState) { final Context context = requireContext(); // 1. 实例化NavHostController mNavController = new NavHostController(context); mNavController.setLifecycleOwner(this); mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher()); mNavController.enableOnBackPressed( mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate); mIsPrimaryBeforeOnCreate = null; mNavController.setViewModelStore(getViewModelStore()); // 2. 创建DialogFragmentNavigator和FragmentNavigator并添加示例到NavigatorProvider中 onCreateNavController(mNavController); Bundle navState = null; if (savedInstanceState != null) { navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE); if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) { mDefaultNavHost = true; getParentFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); } mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID); } if (navState != null) { mNavController.restoreState(navState); } if (mGraphId != 0) { // 3. 设置导航配置文件 mNavController.setGraph(mGraphId); } else { 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); } } super.onCreate(savedInstanceState); }
onCreate方法中主要做三件事:
- 实例化NavHostController对象
- 创建DialogFragmentNavigator和FragmentNavigator并添加到NavHostController的父类NavController的NavigatorProvider类型的成员变量mNavigatorProvider中
- 调用NavHostController#setGraph方法设置导航配置文件nav_graph
public class NavHostController extends NavController { public NavHostController(@NonNull Context context) { super(context); } ... }
主要看父类初始化方法:
public class NavController { private NavigatorProvider mNavigatorProvider = new NavigatorProvider(); public NavController(@NonNull Context context) { ... mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider)); mNavigatorProvider.addNavigator(new ActivityNavigator(mContext)); } }
主要是创建NavGraphNavigator和ActivityNavigator实例并添加到NavController的成员变量mNavigatorProvider中。
4. NavHostFragment#onCreateNavController
@CallSuper protected void onCreateNavController(@NonNull NavController navController) { navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); }
onCreate方法中调用了onCreateNavController方法添加DialogFragmentNavigator和FragmentNavigator示例。
5. NavigatorProvider
public class NavigatorProvider { private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>(); @NonNull static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) { String name = sAnnotationNames.get(navigatorClass); if (name == null) { // 自定义Navigator类的注解Navigator.Name Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class); name = annotation != null ? annotation.value() : null; ... sAnnotationNames.put(navigatorClass, name); } return name; } private final HashMap<String, Navigator<? extends NavDestination>> mNavigators = new HashMap<>() @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) { return mNavigators.put(name, navigator); } }
NavigatorProvider类内部主要是存储了键值为自定义Navigator时注解Navigator.Name指定的名称,值为对应的Navigator示例。
因此onCreate方法执行以后,NavigatorProvider中的mNavigators的值为:
("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(mGraph.getId(), true); } mGraph = graph; onGraphCreated(startDestinationArgs); } @NonNull public NavInflater getNavInflater() { if (mInflater == null) { mInflater = new NavInflater(mContext, mNavigatorProvider); } return mInflater; }
这个方法中首先是实例化NavInflater并调用NavInflater#inflate解析导航配置文件,解析以后的结构存放在NavGraph类中。NavGraph是可以按ID获取的NavDestination节点的树形结构。
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 = 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)) { 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 = res.obtainAttributes( attrs, androidx.navigation.R.styleable.NavInclude); final int id = a.getResourceId( androidx.navigation.R.styleable.NavInclude_graph, 0); ((NavGraph) dest).addDestination(inflate(id)); a.recycle(); } else if (dest instanceof NavGraph) { ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId)); } } return dest; } ... }
NavInflater的主要工作就是解析导航配置文件。接下来再回头看setGraph方法中调用的onGraphCreated方法。
8. NavController#onGraphCreated
private void onGraphCreated(@Nullable Bundle startDestinationArgs) { ... if (mGraph != null && mBackStack.isEmpty()) { boolean deepLinked = !mDeepLinkHandled && mActivity != null && handleDeepLink(mActivity.getIntent()); 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(); } }
刚开始的时候会执行到navigate方法。
9. NavController#navigate
private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = false; boolean launchSingleTop = 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); ... }
根据分析得出getNavigator获取到的Navigator是NavGraphNavigator实例。
10. NavGraphNavigator#navigate
@Nullable @Override public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) { int startId = destination.getStartDestination(); if (startId == 0) { throw new IllegalStateException("no start destination defined via" + " app:startDestination for " + destination.getDisplayName()); } NavDestination startDestination = destination.findNode(startId, false); if (startDestination == null) { final String dest = destination.getStartDestDisplayName(); throw new IllegalArgumentException("navigation destination " + dest + " is not a direct child of this NavGraph"); } Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator( startDestination.getNavigatorName()); return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args), navOptions, navigatorExtras); }
navigate方法中通过startId找到NavDestination变量,再根据NavDestination#getNavigatorName方法获取到的名称得到对应的Navigator实例,此处获取到的是FragmentNavigator实例。
11. FragmentNavigator#navigate
@Nullable @Override public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { ... 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; 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); } ft.replace(mContainerId, frag); ft.setPrimaryNavigationFragment(frag); 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; if (initialNavigation) { isAdded = true; } else if (isSingleTopReplacement) { // 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 { ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId)); isAdded = true; } if (navigatorExtras instanceof Extras) { Extras extras = (Extras) navigatorExtras; for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) { ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue()); } } ft.setReorderingAllowed(true); ft.commit(); // The commit succeeded, update our view of the world if (isAdded) { mBackStack.add(destId); return destination; } else { return null; } }
navigate方法中使用的是FragmentFactory(反射)创建fragment实例。最后通过FragmentTransaction#replace方法添加fragment实例。
再回头看NavHostFragment的生命周期onCreateView方法。
11. NavHostFragment#onCreateView
@Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { FragmentContainerView containerView = new FragmentContainerView(inflater.getContext()); // 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 containerView.setId(getContainerId()); return containerView; }
NavHostFragment默认展示视图是FragmentContainerView。
12. NavHostFragment#onViewCreated
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); Navigation.setViewNavController(view, mNavController); // When added programmatically, we need to set the NavController on the parent - i.e., // the View that has the ID matching this NavHostFragment. if (view.getParent() != null) { mViewParent = (View) view.getParent(); if (mViewParent.getId() == getId()) { Navigation.setViewNavController(mViewParent, mNavController); } } }
public static void setViewNavController(@NonNull View view, @Nullable NavController controller) { view.setTag(R.id.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.getParent(); view = parent instanceof View ? (View) parent : null; } return null; }
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; }
将NavController设置为NavHostFragment的根视图View的tag,以后调用Navigation#findNavController时, 会从传入视图及其所有父视图中找tag,直到找到NavController为止。
从以上分析可以看出,每次调用NavController#navigate方法都会重新生成一个新的Fragment并且调用FragmentTransaction#replace添加,所以每次都会看到重建Fragment的现象。
解决方案
自定义Navigator重写Navigator#navigate方法。
@Navigator.Name("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); this.context = context; this.fragmentManager = fragmentManager; this.containerId = containerId; } @Nullable @Override public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { FragmentTransaction ft = fragmentManager.beginTransaction(); // 获取当前显示的Fragment Fragment fragment = fragmentManager.getPrimaryNavigationFragment(); if (fragment != null) { ft.hide(fragment); } final String tag = String.valueOf(destination.getId()); fragment = fragmentManager.findFragmentByTag(tag); if (fragment != null) { ft.show(fragment); } else { fragment = instantiateFragment(context, fragmentManager, destination.getClassName(), args); ft.add(containerId, fragment, tag); } ft.setPrimaryNavigationFragment(fragment); ft.setReorderingAllowed(true); ft.commit(); return destination; } }
然后通过NavigatorProvider添加即可:
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment); navController.getNavigatorProvider().addNavigator(new CustomNavigator(this, getSupportFragmentManager(), R.id.nav_host_fragment));