Navigation源碼解析
谷歌推出Navigation主要是為了統一應用內頁面跳轉行為。本文主要是根據Navigation版本為2.1.0 的源碼進行講解。
‘androidx.navigation2.1.0’ ‘androidx.navigation2.1.0’ ‘androidx.navigation2.1.0’ ‘androidx.navigation2.1.0’
Navigation的使用很簡單,在創建新項目的時候可以直接選擇 Bottom Navigation Activity 項目,這樣默認就已經幫我們實現了相關頁面邏輯。
Navigation的源碼也很簡單,但是卻涉及到很多的類,主要有以下幾個:
Navigation提供查找NavController方法
NavHostFragment用于承載導航的內容的容器
NavController通過navigate實現頁面的跳轉
Navigator是一個abstract,有四個主要實現類
NavDestination導航節點
NavGraph導航節點頁面集合
我們首先從NavHostFragment入手查看,因為他是直接定義在我們的XML文件中的,我們直接查看器生命周期方法 onCreate :
@CallSuper @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext();
mNavController = new NavHostController(context); //1 mNavController.setLifecycleOwner(this);
。。.。
onCreateNavController(mNavController);//2
。。.。 }
注釋1處 直接創建了NavHostController 并通過 findNavController 方法暴露給外部調用者。NavHostController是繼承自NavController的。注釋2處代碼如下:
@CallSuper protected void onCreateNavController(@NonNull NavController navController) { navController.getNavigatorProvider().addNavigator( new DialogFragmentNavigator(requireContext(), getChildFragmentManager())); navController.getNavigatorProvider().addNavigator(createFragmentNavigator()); }
通過navController獲取NavigatorProvider并向其中添加了兩個Navigator,分別為DialogFragmentNavigator和FragmentNavigator。另外在NavController的構造方法中還添加了另外兩個Navigator,如下:
public NavController(@NonNull Context context) { 。。.。 mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider)); mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));}
他們都是Navigator的實現類。分別對應于DialogFragment、Fragment和Activity的頁面跳轉。大家可能對于NavGraphNavigator一些好奇,它是用在什么地方的呢?其實我們在XML中配置的navGraph對應的navigation跟節點文件中的 startDestination 就是通過NavGraphNavigator來實現跳轉的。這也是它目前唯一的用途。
各個Navigator通過復寫 navigate 方法來實現各自的跳轉邏輯。這里重點強調下 FragmentNavigator 的實現邏輯:
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
。。.。
final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args); frag.setArguments(args); final FragmentTransaction ft = mFragmentManager.beginTransaction();
。。.。
ft.replace(mContainerId, frag); //1
。。.。}
最關鍵的一行代碼就是注釋1 處。他是通過 replace 來加載 Fragment 的 ,這不符合我們實際的開發邏輯。文章后續會講解如何自定義 FragmentNavigator 來避免 Fragment 在切換的時候 生命周期的執行。
回到上文中的 navController 獲取的 NavigatorProvider 其內部是維護了一個HashMap來存儲相關的Navigator信息。通過獲取到Navigator的注解 Name 為key 和 Navigator 的 getClass為 value 進行存儲。
我們在回到上文中的 onCreate 方法:
@CallSuper@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext();
。。.。
if (mGraphId != 0) { mNavController.setGraph(mGraphId); } else {
。。.。
if (graphId != 0) { mNavController.setGraph(graphId, startDestinationArgs); } }}
這里通過 mNavController 調用了 setGraph 。這里主要是為了解析我們的 XML 中配置的mobile_navigation節點信息文件。會根據不同的節點來各自解析。
@NonNullprivate 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 String name = parser.getName(); if (TAG_ARGUMENT.equals(name)) { // argument 節點 inflateArgumentForDestination(res, dest, attrs, graphResId); } else if (TAG_DEEP_LINK.equals(name)) { // deeplink 節點 inflateDeepLink(res, dest, attrs); } else if (TAG_ACTION.equals(name)) { // action 節點 inflateAction(res, dest, attrs, parser, graphResId); } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) { // include 節點 final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavInclude); final int id = a.getResourceId(R.styleable.NavInclude_graph, 0); ((NavGraph) dest).addDestination(inflate(id)); a.recycle(); } else if (dest instanceof NavGraph) { // NavGraph 節點 ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId)); } }
return dest;}
通過獲取 NavInflater 來對其進行解析。解析后返回 NavGraph ,NavGraph是繼承自 NavDestination的。里面主要是保存了所有解析出來的節點信息。
最后簡單的總結下就是通過 NavHostFragment 獲取到NavContorl并存儲了相關的Navigator信息。通過各自的navigate方法進行頁面的跳轉。通過setGraph來解析配置的頁面節點信息,并封裝為NavGraph對象。里面通過SparseArray來存儲 Destination 信息。
自定義Navigator上文中我們說了需要自定義自己的 Navigator 用于承載 Fragment 。主要的實現思路就是繼承現有的 FragmentNavigator 并復寫其 navigate 方法,將其中的 replace 方法 替換為 show 和 hide 方法 來完成 Fragment 的切換。
那么我們自定義的 Navigator 如何才能讓系統識別呢?這也簡單,只要給我們的 類加上注解 @Navigator.Name(value) 那么他就是一個 Navigator 了。最后通過上文中分析的思路 在將其加入到NavigatorProvider 中 即可。
具體的自定義Navigator 已經在項目 Android Jetpack架構開發組件化應用實戰(https://github.com/winlee28/Jetpack-WanAndroid) 中了,類名:FixFragmentNavigator。大家可以自行去看下。這里就將核心的代碼貼出來看下:
@Navigator.Name(“fixFragment”) //新的 Navigator 名稱class FixFragmentNavigator(context: Context, manager: FragmentManager, containerId: Int) : FragmentNavigator(context, manager, containerId) {
override fun navigate( destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Navigator.Extras? ): NavDestination? {
。。.。
//ft.replace(mContainerId, frag)
/** * 1、先查詢當前顯示的fragment 不為空則將其hide * 2、根據tag查詢當前添加的fragment是否不為null,不為null則將其直接show * 3、為null則通過instantiateFragment方法創建fragment實例 * 4、將創建的實例添加在事務中 */ val fragment = mManager.primaryNavigationFragment //當前顯示的fragment if (fragment != null) { ft.hide(fragment) }
var frag: Fragment? val tag = destination.id.toString() frag = mManager.findFragmentByTag(tag) if (frag != null) { ft.show(frag) } else { frag = instantiateFragment(mContext, mManager, className, args) frag.arguments = args ft.add(mContainerId, frag, tag) }
。。.。 }}
自定義完成好,還需要將 mobile_navigation 的節點中遠 fragment 替換為 fixFragment 節點。并刪除布局文件中NavHostFragment 節點的
app:navGraph=“@navigation/mobile_navigation”
信息,因為我們需要手動將 FixFragmentNavigator 和 NavControl 進行關聯。
//添加自定義的FixFragmentNavigatornavController = Navigation.findNavController(this, R.id.nav_host_fragment)val fragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragmentval fragmentNavigator = FixFragmentNavigator(this, supportFragmentManager, fragment?。?id)navController.navigatorProvider.addNavigator(fragmentNavigator)
navController.setGraph(R.navigation.mobile_navigation)
這樣就完成了自定義 Navigator 實現切換 Tab 的時候 Fragment 生命周期不會重新執行了。
具體代碼邏輯詳見:Android Jetpack架構開發組件化應用實戰(https://github.com/winlee28/Jetpack-WanAndroid)
編輯:jq
-
Android
+關注
關注
12文章
3935瀏覽量
127350 -
XML
+關注
關注
0文章
188瀏覽量
33078 -
源碼
+關注
關注
8文章
639瀏覽量
29185
原文標題:Navigation源碼解析及自定義FragmentNavigator詳解
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論