赞
踩
在使用Navigation库是,其固定模式是在Activity的布局文件中会先放一个NavHostFragment,其必须包含一个属性app:navGraph,指向navigation类型的资源文件,该文件以树结构形式定义了所有fagment交互的方式。但是当该Activity启动完成后却没有发现NavHostFragment的踪影,因此通过源码来分析Navigation初始化NavHostFragment过程。以解决以下疑惑:
google官方使用教程指出:navogation的初始化依赖于一个特殊的Fragment NavHostFragment,在布局中<fragment>标签中需要指明属性app:navGraph,不然会抛出异常,其值为navigation布局文件的名称,也即<navigation>下的内容。如下使用场景中,MainActivity的布局文件为:
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.android.codelabs.navigation.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> // 省略部分布局 <fragment android:id="@+id/my_nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/mobile_navigation" /> </LinearLayout> </androidx.drawerlayout.widget.DrawerLayout>
mobile_navigation的文件为:
<navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" app:startDestination="@+id/home_dest"> <fragment android:id="@+id/home_dest" android:name="com.example.android.codelabs.navigation.HomeFragment" android:label="@string/home" tools:layout="@layout/home_fragment"> <action android:id="@+id/next_action" app:destination="@+id/flow_step_one_dest" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left" app:popEnterAnim="@anim/slide_in_left" app:popExitAnim="@anim/slide_out_right" /> </fragment> </navigation>
在<navigation>标签下属性app:startDestination指明了该<navigation>标签树中第一个显示的fragment,因此MainActivity启动后显示的Fragment的id为@+id/home_dest。
下面看一个从MainActicity启动并显示HomeFragment的过程。首先代码的入口是MainActivity中的onCreat(),会执行setContentView(R.layout.navigation_activity),其中会调用LayoutInflater.inflate()来解析xml布局文件。MainActivity继承于AppCompatActivity,间接继承于FragmentActivity,接着会执行FragmentActivity生命周期相关方法onCreateView()方法,在该方法中进一步调用dispatchFragmentsOnCreateView()方法来分发onCreateView给fragments:
final View dispatchFragmentsOnCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
return mFragments.onCreateView(parent, name, context, attrs);
}
这里的mFragments是一个FragmentController对象,其主要作用是对mHost进行代理,mHost是一个FragmentHostCallback对象:
//FragmentController.java
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
return mHost.mFragmentManager.onCreateView(parent, name, context, attrs);
}
//FragmentHostCallback.java
public abstract class FragmentHostCallback<E> extends FragmentContainer {
@Nullable private final Activity mActivity;
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
private final int mWindowAnimations;
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
}
可以看出FragmentHostCallback引用了mActivity、mContext等全局变量,且其在FragmentController的名字叫mHost,并由FragmentActivity传入该匿名对象,可知其作用就是将fragment的操作传递给其宿主FragmentActivity。最后fragmen的创建是在FragmentManagerImpl中完成的:
//FragmentManagerImpl.java public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (!"fragment".equals(name)) { return null; } //fname的值就是xml中设置的属性值androidx.navigation.fragment.NavHostFragment String fname = attrs.getAttributeValue(null, "class"); TypedArray a = context.obtainStyledAttributes(attrs, FragmentTag.Fragment); if (fname == null) { fname = a.getString(FragmentTag.Fragment_name); } int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID); String tag = a.getString(FragmentTag.Fragment_tag); a.recycle(); int containerId = parent != null ? parent.getId() : 0; if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { throw new IllegalArgumentException(attrs.getPositionDescription() + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname); } Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null; if (fragment == null && tag != null) { fragment = findFragmentByTag(tag); } if (fragment == null && containerId != View.NO_ID) { fragment = findFragmentById(containerId); } //第一次启动时,上面所有findFragmen操作都会为null if (fragment == null) { fragment = getFragmentFactory().instantiate(context.getClassLoader(), fname); fragment.mFromLayout = true; fragment.mFragmentId = id != 0 ? id : containerId; fragment.mContainerId = containerId; fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; fragment.mHost = mHost; fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); addFragment(fragment, true); } else if (fragment.mInLayout) { //省略 } else { //省略 } if (mCurState < Fragment.CREATED && fragment.mFromLayout) { moveToState(fragment, Fragment.CREATED, 0, 0, false); } else { moveToState(fragment); } if (fragment.mView == null) { throw new IllegalStateException("Fragment " + fname + " did not create a view."); } if (id != 0) { fragment.mView.setId(id); } if (fragment.mView.getTag() == null) { fragment.mView.setTag(tag); } return fragment.mView; }
这里通findFragment均为null,因此会通过ClassLorder加载名为fname的类,并实例化一个对象。在执行NavHostFragment的onInflate()方法时主要解析了xml中属性名app:navGraph的值,并保存在mGraphId字段中,用于下一步生成跳转图对象。接着调用addFragment()在FragmentManagerImpl的调用链为:
下面看一下NavHostFragment类中onCreate()方法的实现:
//NavHostFragment.java public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Context context = requireContext(); //2.1. 初始化NavHostController实例 mNavController = new NavHostController(context); mNavController.setLifecycleOwner(this); mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher()); // Set the default state - this will be updated whenever // onPrimaryNavigationFragmentChanged() is called mNavController.enableOnBackPressed( mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate); mIsPrimaryBeforeOnCreate = null; mNavController.setViewModelStore(getViewModelStore()); onCreateNavController(mNavController); //在onSaveInstanceState()会以KEY_NAV_CONTROLLER_STATE为key缓存mNavController中的一个Bundle对象 Bundle navState = null; if (savedInstanceState != null) { navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE); if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) { mDefaultNavHost = true; requireFragmentManager().beginTransaction() .setPrimaryNavigationFragment(this) .commit(); } } if (navState != null) { // Navigation controller state overrides arguments mNavController.restoreState(navState); } if (mGraphId != 0) { //2.2. 调用NavHostController实例解析mGraphId //初始化时会走到这里 // Set from onInflate() mNavController.setGraph(mGraphId); } else { // See if it was set by NavHostFragment.create() 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); } } }
这段代码比较长,分段看一下:
首先看一下new操作做了什么:
public NavController(@NonNull Context context) {
mContext = context;
//确保mActivity为Activity对象
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
mActivity = (Activity) context;
break;
}
context = ((ContextWrapper) context).getBaseContext();
}
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
主要就是往类型为NavigatorProvider的mNavigatorProvider中添加一个NavGraphNavigator对象和一个ActivityNavigator对象,NavGraphNavigator即是初始化NavHostFragment时会用来跳转到app:startDestination属性值。添加的过程会是分别获取该类的注解Navigator.Name值做为key,相应对象做为value然后存储在NavigatorProvider中名为mNavigators的Map中。之后会设置mNavController的生命周期宿主、按Back键的分发器等,然后将该NavHostFragment的ViewModel的存储对象设置给了mNavController,在看一下onCreateNavController()中代码:
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
}
其操作和上面很相似,都是往mNavigatorProvider对象中添加用于DialogFragment跳转的DialogFragmentNavigator对象和用于Fragment跳转的对象
FragmentNavigator。
通过注释可知在onInflate中解析并保存了mGraphId值,所以现在需要解析mGraphId指代的<navigation>文件,内部代码:
//NavController.java
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
可以看到用NavInflater解析该xml文件并生成一个NavGraph对象,并设置给NavController中的mGraph字段保存。看一下解析过程:
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, 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) dest).addDestination(inflate(res, parser, attrs, graphResId)); } } return dest; }
通过前面分析mNavigatorProvider已经包含了四组键值对:
这里parser解析的xml文件的顶层标签是<navigation>,因此调用其getName()方法返回值为"navigation",这正好和NavGraphNavigator类的Navigator.Name注解值是一致的,因此在mNavigatorProvider中获取的navigator实际是一个NavGraphNavigator对象,通过其createDestination()方法获得的dest是一个NavGraph对象,在执行其onInflate()方法:
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.NavGraphNavigator);
setStartDestination(
a.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0));
mStartDestIdName = getDisplayName(context, mStartDestId);
a.recycle();
}
先是获取<navigation>标签下startDestination属性的资源id,并保存在mStartDestId字段中,然后通过mStartDestId获取mStartDestIdName,这里其值为com.example.android.codelabs.navigation:id/home_dest。因为dest类型为NavGraph,因此进入循环后会执行 ((NavGraph) dest).addDestination()方法:
//NavGraph.java public final void addDestination(@NonNull NavDestination node) { if (node.getId() == 0) { throw new IllegalArgumentException("Destinations must have an id." + " Call setId() or include an android:id in your navigation XML."); } NavDestination existingDestination = mNodes.get(node.getId()); if (existingDestination == node) { return; } if (node.getParent() != null) { throw new IllegalStateException("Destination already has a parent set." + " Call NavGraph.remove() to remove the previous parent."); } if (existingDestination != null) { existingDestination.setParent(null); } node.setParent(this); mNodes.put(node.getId(), node); }
通过node.setParent(this);可知<navagation>标签被解析成的NavGraph是所有节点的父亲节点,NavGraph内部类型为SparseArrayCompat的mNodes存储了所有子节点。
解析完<navagation>标签后,会递归调用inflate(),这一次parser.getName()返回的值为"fragment",因此此次获取的navigator的实际类型是FragmentNavigator,其createDestination()方法返回一个Destination对象,看一下该对象的onInflate()方法:
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.FragmentNavigator);
String className = a.getString(R.styleable.FragmentNavigator_android_name);
if (className != null) {
setClassName(className);
}
a.recycle();
}
回顾前面定义<navigation>的xml文件可知这里的className = com.example.android.codelabs.navigation.HomeFragment,并把它保存在mClassName字段中。
进入while循环parser.next()会指向<action>标签,因此循环内的name = “action”,因此会进入inflateAction()分支:
private void inflateAction(@NonNull Resources res, @NonNull NavDestination dest, @NonNull AttributeSet attrs, XmlResourceParser parser, int graphResId) throws IOException, XmlPullParserException { final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavAction); final int id = a.getResourceId(R.styleable.NavAction_android_id, 0); final int destId = a.getResourceId(R.styleable.NavAction_destination, 0); NavAction action = new NavAction(destId); NavOptions.Builder builder = new NavOptions.Builder(); builder.setLaunchSingleTop(a.getBoolean(R.styleable.NavAction_launchSingleTop, false)); builder.setPopUpTo(a.getResourceId(R.styleable.NavAction_popUpTo, -1), a.getBoolean(R.styleable.NavAction_popUpToInclusive, false)); builder.setEnterAnim(a.getResourceId(R.styleable.NavAction_enterAnim, -1)); builder.setExitAnim(a.getResourceId(R.styleable.NavAction_exitAnim, -1)); builder.setPopEnterAnim(a.getResourceId(R.styleable.NavAction_popEnterAnim, -1)); builder.setPopExitAnim(a.getResourceId(R.styleable.NavAction_popExitAnim, -1)); action.setNavOptions(builder.build()); Bundle args = new Bundle(); 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)) { inflateArgumentForBundle(res, args, attrs, graphResId); } } if (!args.isEmpty()) { action.setDefaultArguments(args); } dest.putAction(id, action); a.recycle(); }
看到通过解析<action>标签下的属性,并构造一个类型为NavAction的对象action,中间的while循环主要是解析标签,最后以该<action>标签的id为key,解析的action对象为值,存储在dest对象内部的类型为SparseArrayCompat的mActions中。
在2.2这一步中将<navigation>资源文件解析成NavGraph,并调用setGragh()方法设置给mGraph:
//NavController.java public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) { if (mGraph != null) { // Pop everything from the old graph off the back stack popBackStackInternal(mGraph.getId(), true); } mGraph = graph; onGraphCreated(startDestinationArgs); } 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); } } }
可以看到在启动无deepLinked的情况下会调用navigate()方法进行跳转:
//NavController.java private void navigate(@NonNull NavDestination node, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { boolean popped = 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); if (newDest != null) { if (!(newDest instanceof FloatingWindow)) { // We've successfully navigating to the new destination, which means // we should pop any FloatingWindow destination off the back stack // before updating the back stack with our new destination //noinspection StatementWithEmptyBody while (!mBackStack.isEmpty() && mBackStack.peekLast().getDestination() instanceof FloatingWindow && popBackStackInternal( mBackStack.peekLast().getDestination().getId(), true)) { // Keep popping } } // The mGraph should always be on the back stack after you navigate() if (mBackStack.isEmpty()) { mBackStack.add(new NavBackStackEntry(mGraph, finalArgs, mViewModel)); } // Now ensure all intermediate NavGraphs are put on the back stack // to ensure that global actions work. ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>(); NavDestination destination = newDest; while (destination != null && findDestination(destination.getId()) == null) { NavGraph parent = destination.getParent(); if (parent != null) { hierarchy.addFirst(new NavBackStackEntry(parent, finalArgs, mViewModel)); } destination = parent; } mBackStack.addAll(hierarchy); // And finally, add the new destination with its default args NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, newDest.addInDefaultArgs(finalArgs), mViewModel); mBackStack.add(newBackStackEntry); } updateOnBackPressedCallbackEnabled(); if (popped || newDest != null) { dispatchOnDestinationChanged(); } }
这里传入的参数node实际类型是NavGragh,navOptions = null,通过前面分析可知node.getNavigatorName()所得结果为“navigation”,这里navigator即为一个NavGraphNavigator对象,其navigation()代码为:
//NavGraphNavigator.java 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); }
通过<navigation>xml文件可知这里的startId和<fragment>标签下的id是同一个,前文分析了NavGragh是所以节点的祖先节点,其内部字段mNodes以节点id为key保存了其子节点,因此调用NavGragh的findNode()方法实际拿到一个对应<fragment>标签的Destination对象startDestination,因此这里的navigator即是一个FragmentNavigator,进一步调用其navigate()方法:
//FragmentNavigator.java public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) { if (mFragmentManager.isStateSaved()) { Log.i(TAG, "Ignoring navigate() call: FragmentManager has already" + " saved its state"); return null; } 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; } ft.setReorderingAllowed(true); ft.commit(); // The commit succeeded, update our view of the world if (isAdded) { mBackStack.add(destId); return destination; } else { return null; } }
这里的className = com.example.android.codelabs.navigation.HomeFragment,通过调用instantiateFragment()使用ClassLoader初始化该HomeFragment对象,这里的mContainerId是NavHostFragment的id,也即是xml中定义的@+id/my_nav_host_fragment,然后给HomeFragment对象设置了argument参数、进入退出动画,然后采用replace方式将NavHostFragment替换为HomeFragment。因为是初始化,所以initialNavigation = true,因此isAdded = true。这里的destination对应HomeFragment,destId即为@+id/home_dest,最后将其存储在mBackStack中。至此,已经完成MainActivity初始化 -> NavHostFragment初始化 -> HomeFragment初始化 -> NavHostFragment被替换为HomeFragment这一过程的分析。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。