Android?Navigation重建Fragment問題分析及解決
前言
最近項(xiàng)目中使用到了BottomNavigationView結(jié)合Navigation實(shí)現(xiàn)底部導(dǎo)航欄切換頁(yè)面業(yè)務(wù)。
NavigationUI.setupWithNavController(bottomNavigationView, navController);
結(jié)果發(fā)現(xiàn)每次點(diǎn)擊底部導(dǎo)航欄切換的時(shí)候都會(huì)重建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>
當(dāng)調(diào)用NavigationUI.setupWithNavController(BottomNavigationView, NavController)以后,setupWithNavController方法內(nèi)部其實(shí)通過調(diào)用BottomNavigationView#setOnNavigationItemSelectedListener方法監(jiān)聽導(dǎo)航欄選中事件。
在BottomNavigationView.OnNavigationItemSelectedListener監(jiān)聽中,最終會(huì)調(diào)用到NavController#navigate方法,進(jìn)入Navigation源碼中。
Navigation源碼分析
首先看NavHostFragment的執(zhí)行流程。
1. NavHostFragment#onInflate
因?yàn)樵趚ml中聲明fragment因此,首先調(diào)用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
根據(jù)Fragment生命周期,然后執(zhí)行的是onAttach方法。
@CallSuper @Override public void onAttach(@NonNull Context context) { super.onAttach(context); if (mDefaultNavHost) { getParentFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); } }
onAttach方法中主要是設(shè)置NavHostFragment為導(dǎo)航器的主導(dǎo)航容器。
3. NavHostFragment#onCreate
@CallSuper @Override public void onCreate(@Nullable Bundle savedInstanceState) { final Context context = requireContext(); // 1. 實(shí)例化NavHostController mNavController = new NavHostController(context); mNavController.setLifecycleOwner(this); mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher()); mNavController.enableOnBackPressed( mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate); mIsPrimaryBeforeOnCreate = null; mNavController.setViewModelStore(getViewModelStore()); // 2. 創(chuàng)建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. 設(shè)置導(dǎo)航配置文件 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方法中主要做三件事:
- 實(shí)例化NavHostController對(duì)象
- 創(chuàng)建DialogFragmentNavigator和FragmentNavigator并添加到NavHostController的父類NavController的NavigatorProvider類型的成員變量mNavigatorProvider中
- 調(diào)用NavHostController#setGraph方法設(shè)置導(dǎo)航配置文件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)); } }
主要是創(chuàng)建NavGraphNavigator和ActivityNavigator實(shí)例并添加到NavController的成員變量mNavigatorProvider中。
4. NavHostFragment#onCreateNavController
@CallSuper protected void onCreateNavController(@NonNull NavController navController) { navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); }
onCreate方法中調(diào)用了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類內(nèi)部主要是存儲(chǔ)了鍵值為自定義Navigator時(shí)注解Navigator.Name指定的名稱,值為對(duì)應(yīng)的Navigator示例。
因此onCreate方法執(zhí)行以后,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; }
這個(gè)方法中首先是實(shí)例化NavInflater并調(diào)用NavInflater#inflate解析導(dǎo)航配置文件,解析以后的結(jié)構(gòu)存放在NavGraph類中。NavGraph是可以按ID獲取的NavDestination節(jié)點(diǎn)的樹形結(jié)構(gòu)。
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的主要工作就是解析導(dǎo)航配置文件。接下來再回頭看setGraph方法中調(diào)用的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(); } }
剛開始的時(shí)候會(huì)執(zhí)行到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); ... }
根據(jù)分析得出getNavigator獲取到的Navigator是NavGraphNavigator實(shí)例。
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變量,再根據(jù)NavDestination#getNavigatorName方法獲取到的名稱得到對(duì)應(yīng)的Navigator實(shí)例,此處獲取到的是FragmentNavigator實(shí)例。
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(反射)創(chuàng)建fragment實(shí)例。最后通過FragmentTransaction#replace方法添加fragment實(shí)例。
再回頭看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默認(rèn)展示視圖是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設(shè)置為NavHostFragment的根視圖View的tag,以后調(diào)用Navigation#findNavController時(shí), 會(huì)從傳入視圖及其所有父視圖中找tag,直到找到NavController為止。
從以上分析可以看出,每次調(diào)用NavController#navigate方法都會(huì)重新生成一個(gè)新的Fragment并且調(diào)用FragmentTransaction#replace添加,所以每次都會(huì)看到重建Fragment的現(xiàn)象。
解決方案
自定義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(); // 獲取當(dāng)前顯示的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));
到此這篇關(guān)于Android Navigation重建Fragment問題分析及解決的文章就介紹到這了,更多相關(guān)Android Navigation 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android程序啟動(dòng)時(shí)出現(xiàn)黑屏問題的解決方法
這篇文章主要介紹了Android程序啟動(dòng)時(shí)出現(xiàn)黑屏問題的解決方法,分析了黑屏出現(xiàn)的原因及相應(yīng)的解決方法,需要的朋友可以參考下2016-08-08android下拉刷新ListView的介紹和實(shí)現(xiàn)代碼
在當(dāng)下,列表組件不帶下拉刷新的都不好意思叫列表。第一次完成列表的下拉刷新功能的時(shí)候,直接在Activity中實(shí)現(xiàn),雖然功能上是實(shí)現(xiàn)了,總體上感覺很亂。所以第二次用到的時(shí)候,就想著封裝成一個(gè)組件,實(shí)現(xiàn)和Activity的解耦。2013-04-04React-Native Android 與 IOS App使用一份代碼實(shí)現(xiàn)方法
這篇文章主要介紹了React-Native Android 與 IOS App使用一份代碼實(shí)現(xiàn)方法的相關(guān)資料,這里舉例說明,該如何實(shí)現(xiàn)IOS和Android APP 都使用一樣的代碼,需要的朋友可以參考下2016-12-12Android開發(fā)之ToggleButton實(shí)現(xiàn)開關(guān)效果示例
這篇文章主要介紹了Android開發(fā)之ToggleButton實(shí)現(xiàn)開關(guān)效果的方法,結(jié)合實(shí)例形式分析了ToggleButton控件實(shí)現(xiàn)開關(guān)效果的布局與功能相關(guān)操作技巧,需要的朋友可以參考下2017-07-07Android使用友盟集成QQ、微信、微博等第三方分享與登錄方法詳解
之前的項(xiàng)目第三方分享和登錄一直都使用ShareSDK實(shí)現(xiàn)的。為了統(tǒng)一使用友盟的全家桶,所以三方分享和登錄也就選擇了友盟,這里為大家整理出詳細(xì)方法2018-03-03Android ListView構(gòu)建支持單選和多選的投票項(xiàng)目
如何在Android的ListView中構(gòu)建CheckBox和RadioButton列表?這篇文章主要為大家詳細(xì)介紹了Android ListView實(shí)現(xiàn)支持單選和多選的投票項(xiàng)目,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Android學(xué)習(xí)教程之2D繪圖基礎(chǔ)及繪制太極圖
這篇文章主要給大家介紹了Android中2D繪圖基礎(chǔ)的相關(guān)資料,文中介紹了繪圖的基礎(chǔ)內(nèi)容,以及通過Canvas和Paint實(shí)現(xiàn)繪制太極圖的詳細(xì)過程,對(duì)各位Android新手開發(fā)者們具有一定的參考價(jià)值,需要的朋友下面來一起看看吧。2017-04-04