Android Navigation重建Fragment问题分析及解决
最近项目中使用到了BottomNavigationView结合Navigation实现底部导航栏切换页面业务。结果发现每次点击底部导航栏切换的时候都会重建Fragment,于是分析了源码,并研究了解决方案。
前言
最近项目中使用到了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
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方法。
更多推荐
所有评论(0)