最近项目中使用到了BottomNavigationView结合Navigation实现底部导航栏切换页面业务。

NavigationUI.setupWithNavController(bottomNavigationView, navController);

结果发现每次点击底部导航栏切换的时候都会重建Fragment,于是分析了源码,并研究了解决方案。

源码分析

首先看布局文件:

xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent">

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" />

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" />

当调用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, String> sAnnotationNames = new HashMap<>();

@NonNull

static String getNameForNavigator(@NonNull Class 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> mNavigators = new HashMap<>();

@Nullable

public final Navigator addNavigator(

@NonNull Navigator navigator) {

String name = getNameForNavigator(navigator.getClass());

return addNavigator(name, navigator);

}

@CallSuper

@Nullable

public Navigator addNavigator(@NonNull String name,

@NonNull Navigator 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 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 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 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) 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));

参考

生命周期

感谢大家的支持,如有错误请指正,如需转载请标明原文出处!

推荐文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: