了解Navigation使用后,思考几个问题
- NavHostFragmnet作为路由容器,是如何解析nav_graph资源文件,从而生成NavGraph对象?
- 跳转时,路由是如何被执行的?
- 跳转的路由目标节点,NavDestination又是如何创建的。
- 分析后是否能总结出Navigation的优点和痛点
- 能否解决痛点,该如何解决,有什么思路?
源码分析从下面的图入手:
版本:
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
未命名文件.png
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
核心类介绍:
NavHostFragment: 所有节点的外部容器
NavController: 外部调用的入口,提供路由,回退等核心操作
NavDestination 节点的封装类对应nav_graph.xml文件中的 </navigation>, </fragment> </activity>, </dialog>目标节点(即Destination),同时有如四个子类:NavGraph,FragmentNavigator#Destination,ActivityNavigator#Destination,DialogFragmentNavigator#Destination
NavGraph 特殊的Destination,将app:navGraph="@navigation/nav_graph解析封装成NavGraph对象,里面包含nav_graph.xml中的所有信息。根节点为</navigation>
Navigator 抽象类。NavController中的navigation()会转到它的子类,包括NavGraphNavigator,ActivityNavigator,FragmentNavigator,DialogFragmentNavigator。他们会重写Navigator的navigation()方法,实现自己的跳转逻辑
NavigatorProvider: 是各种Navigator的管理者,想要定义自己的Navigator,就必须想这个类里的map进行注册
源码分析
理解上面类的作用,我们从容器开始入手,看NavHostFragment,是如何获取xml中配置的属性:
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
android:name="androidx.navigation.fragment.NavHostFragment"
xml中的属性,通常在AttributeSet attrs
中获取,但Fragment的构造函数显然不会有此属性。但我们在此类中发现下面的函数:
@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);
//1
final int graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0);
if (graphId != 0) {
//2
mGraphId = graphId;
}
navHost.recycle();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
//3
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
if (defaultHost) {
//4
mDefaultNavHost = true;
}
a.recycle();
}
View(ViewGroup),fragment等在可以在XML中定义的标签,在绘制结束后,会执行onInflate()方法。通过1.处解析,得到nav_graph资源id,并保存在mGraphId变量;3.处解析获取 app:defaultNavHost="true"
设置的参数。
接下来看OnCreate()方法:
//1
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
//2
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());
//3
onCreateNavController(mNavController);
if (mGraphId != 0) {//4
// 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);
}
}
核心代码如上,接下里是分步走分析
1. mNavController = new NavHostController(context);
跟踪:
public NavHostController(@NonNull Context context) {
super(context);
}
NavHostController这个类没有任何逻辑,什么都没做,目的就是为了和NavHostFragment在形式上统一,直接去看父类: super(context);
public NavController(@NonNull Context context) {
mContext = context;
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));
}
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider))
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
刚开始就在Navigator管理器NavigatorProvider这个添加NavGraphNavigator和ActivityNavigator跳转类。这样做的理由是,Navigation框架作为路由导航,可以不用Fragment和Dialog,但不能没有启动页和Activity路由跳转类。换句话说,我们的App可以没有Fragment和Dialog。但不能没有Activity。而Navigation框架不允许没有启动首页,所以必须有NavGraphNavigator这个启动首页的跳转路由类。
继续跟踪:mNavigatorProvider.addNavigator(new ActivityNavigator(mContext))
NavigatorProvider:
@Nullable
public final Navigator<? extends NavDestination> addNavigator(
@NonNull Navigator<? extends NavDestination> navigator) {
//name为 activity
String name = getNameForNavigator(navigator.getClass());
//1.下面会分析这里,记得回头看
return addNavigator(name, navigator);
}
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
// annotation此时为:activity/fragment/dialog,navigation
//2. 下面会分析这里,记住这个位置
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
name = annotation != null ? annotation.value() : null;
if (!validateName(name)) {
throw new IllegalArgumentException("No @Navigator.Name annotation found for "
+ navigatorClass.getSimpleName());
}
//3. 下面会分析这里,记得回头看
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
Navigator.Name 是个注解类,他会用在所有Navigator所有子类的类头,用来标记 子类是什么类型的Navigator,如下:
Activity:
@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination>
Dialog:
@Navigator.Name("dialog")
public final class DialogFragmentNavigator extends Navigator<DialogFragmentNavigator.Destination>
Navigation:
@Navigator.Name("navigation")
public class NavGraphNavigator extends Navigator<NavGraph
Fragment:
@Navigator.Name("fragment")
public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
恰好对应nav_graph.xml中4中Destination标签,侧面验证他们都具有各自的navigation()跳转逻辑。
继续回到 getNameForNavigator()
方法。
2. annotation此时为:activity/fragment/dialog,navigation
3. sAnnotationNames.put(navigatorClass, name);
存放的格式为put(ActivityNavigator.class,activity)
1. 回到上面的1.位置:
public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
@NonNull Navigator<? extends NavDestination> navigator) {
if (!validateName(name)) {
throw new IllegalArgumentException("navigator name cannot be an empty string");
}
return mNavigators.put(name, navigator);
}
mNavigators为HashMap<String, Navigator<? extends NavDestination>> mNavigators
此类中最核心的管理类,添加进去存放的格式为put(activity,ActivityNavigator.class)
同时 返回Navigator对应的NavDestination 很重要
以上第一部分完成。总结如下:
-
NavHostController
这个类没啥实际作用,就是为了和NavHostFragment形式上同样,真正的实现都在父类NavController中 - 想要自定义自己的Navigator,必须继承Navigator,并且在类头上定义自己的Navigator.Name。
- 自定义的Navigator,必须加入NavigatorProvider#mNavigators这个Map中注册Navigator.Name的value就是Map的key
深入的有点深,此时回头看往上看OnCreate()方法2处
2.mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
此方法是实现fragment回退的关键,requireActivity().getOnBackPressedDispatcher()
这个是Activity返回键监听的分发器OnBackPressedDispatcher
。
NavController:
void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
if (mLifecycleOwner == null) {
throw new IllegalStateException("You must call setLifecycleOwner() before calling "
+ "setOnBackPressedDispatcher()");
}
// Remove the callback from any previous dispatcher
mOnBackPressedCallback.remove();
// Then add it to the new dispatcher
dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
...略
}
dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
将获得的OnBackPressedDispatcher
对象传入,并向里面注册监听OnBackPressedDispatcher
:
OnBackPressedDispatcher:
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
popBackStack();
}
};
注册监听后,当dispatcher分发返回键点击事件时,会回调我们注册的监听,从而调用popBackStack();
出栈方法
总结:
- 给我们个提示,如果我们有需求要拦截返回键,做我们想做的事情,可以像dispatcher注册我们自己的监听回调。
此时回头看往上看OnCreate()方法3处
3. onCreateNavController(mNavController);
跟踪:
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(),
getContainerId());
}
添加DialogFragmentNavigator和FragmentNavigator跳转支持,此时4种Navigator,全部添加进NavigationProvider的HashMap中。支持4中标跳转的能力
总结:
- Navigation路由跳转的容器必须是NavHostFragment,否则无法支持Dialog和Fragment跳转能力
- 如果自定义FragmentNavigator和DialogFragmentNavigator类型,传入
的FragmentManager
是getChildFragmentManager()
此时回头看往上看OnCreate()方法4处:
4. onCreateNavController(mNavController);
if (mGraphId != 0) {//4
// 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);
}
}
无论走哪个分支,必然调用mNavController.setGraph()
方法:
在这里暂停,下面跟随代码深入,会越来越深,但思路清晰,暂且在这里设置个锚点1,会说回到锚点1 就是setGraph()这个方法
锚点1
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
getNavInflater():
public NavInflater getNavInflater() {
if (mInflater == null) {
mInflater = new NavInflater(mContext, mNavigatorProvider);
}
return mInflater;
}
创建:mInflater,接着进入mInflater#inflate():
@SuppressLint("ResourceType")
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
Resources res = mContext.getResources();
XmlResourceParser parser = res.getXml(graphResId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
String rootElement = parser.getName();
// 1
NavDestination destination = inflate(res, parser, attrs, graphResId);
if (!(destination instanceof NavGraph)) {
throw new IllegalArgumentException("Root element <" + rootElement + ">"
+ " did not inflate into a NavGraph");
}
2.
return (NavGraph) destination;
} catch (Exception e) {
throw new RuntimeException("Exception inflating "
+ res.getResourceName(graphResId) + " line "
+ parser.getLineNumber(), e);
} finally {
parser.close();
}
}
这个方法的返回值为NavGraph,这点要记住。也就是1处返回的对象destination,实际是NavGraph,所以在2处强转返回.跟进1处代码:
锚点2
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException {
//1
Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
//2
final NavDestination dest = navigator.createDestination();
//3.
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);
//4
((NavGraph) dest).addDestination(inflate(id));
a.recycle();
} else if (dest instanceof NavGraph) {
//5
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
}
return dest;
}
这个方法很重要。首先我们知道NavGraph中是包含nav_graph所有节点的内容。所以进入方法时是<navigation>这个根节点标签,1.中navigator=NavGraphNavigator 2.navigator.createDestination()就是dest=new NavGraph(this) this=NavGraphNavigator:
跟进:
public NavGraph createDestination() {
return new NavGraph(this);
}
public NavGraph(@NonNull Navigator<? extends NavGraph> navGraphNavigator) {
super(navGraphNavigator);
}
public NavDestination(@NonNull Navigator<? extends NavDestination> navigator) {
this(NavigatorProvider.getNameForNavigator(navigator.getClass()));
}
3,中调用的onInflate()则为NavGraph类中的
NavGraph:
@Override
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();
}
public final void setStartDestination(@IdRes int startDestId) {
if (startDestId == getId()) {
throw new IllegalArgumentException("Start destination " + startDestId + " cannot use "
+ "the same id as the graph " + this);
}
mStartDestId = startDestId;
mStartDestIdName = null;
}
setStartDestination()根据startDestination就是我们在nav_graph的根节点设置的app:startDestination="@+id/navigation_home"
参数,赋值mStartDestId和mStartDestIdName,这里我们知道,当<navigation>嵌套时,不能使用相同的app:startDestination="@+id/navigation_home"
ID
继续回到4
解析完根节点后,会在循环中,进入到4或5️,然后递归调用。递归中dest分别可能为Navigator的另外三个子类ActivityNavigator,DialogFragmentNavigator,FragmentNavigator
然后分别调用他们的navigator.createDestination()方法。然后分别调用:dest.onInflate(mContext, attrs)
逐一解析:ActivityNavigator#Destination#onInflate( Context context, AttributeSet attrs)
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.ActivityNavigator);
String targetPackage = a.getString(R.styleable.ActivityNavigator_targetPackage);
if (targetPackage != null) {
targetPackage = targetPackage.replace(NavInflater.APPLICATION_ID_PLACEHOLDER,
context.getPackageName());
}
//1
setTargetPackage(targetPackage);
String className = a.getString(R.styleable.ActivityNavigator_android_name);
if (className != null) {
if (className.charAt(0) == '.') {
//2
className = context.getPackageName() + className;
}
//3
setComponentName(new ComponentName(context, className));
}
//4
setAction(a.getString(R.styleable.ActivityNavigator_action));
String data = a.getString(R.styleable.ActivityNavigator_data);
if (data != null) {
//5
setData(Uri.parse(data));
}
setDataPattern(a.getString(R.styleable.ActivityNavigator_dataPattern));
a.recycle();
}
解析<activity>标签1.获取包名 2.得到全类名 3.4.5进入方法内部,都是对Intent进行赋值。我们知道,设置ComponentName,我们就可以进行最基本的跳转,如下:
public final Destination setComponentName(@Nullable ComponentName name) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setComponent(name);
return this;
}
而且从xml代码中可以看出,id,name是必要参数,有他们2就可以路由跳转
<activity
android:id="@+id/navigation_home"
android:name="org.devio.proj.navigationpro.NavigationActivity"
android:label="@string/title_home"
tools:layout="@layout/activity_main" />
结论:onInflate()方法的核心是setComponentName(),也就是说当我们自定义Navigatior时,要配置setComponentName()
逐一解析:FragmentNavigator#Destination#onInflate( Context context, AttributeSet attrs)
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();
}
这个方法就很简单, setClassName(className);
保存Fragment的全类名
逐一解析:DialogFragmentNavigator#Destination#onInflate( Context context, AttributeSet attrs)
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.DialogFragmentNavigator);
String className = a.getString(R.styleable.DialogFragmentNavigator_android_name);
if (className != null) {
setClassName(className);
}
a.recycle();
}
也是如此
回到锚点2
private static final String TAG_ARGUMENT = "argument";1
private static final String TAG_DEEP_LINK = "deepLink";2
private static final String TAG_ACTION = "action";3
private static final String TAG_INCLUDE = "include";4
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));
}
通过对1,2,3,4子标签的解析,然后存到对应的NavDestination中,。同时在递归中把所有NavDestination节点addDestination()到NavGraph中:
NavGraph:
SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
public final void addDestination(@NonNull NavDestination node) {
...略
mNodes.put(node.getId(), node);
}
总结: 所有NavDestination都要存入mNodes
回到锚点1
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(mGraph, startDestinationArgs, null, null);
startDestinationArgs,意味着跳转路由,启动第一个app:startDestination="@+id/navigation_home"
配置的节点页面
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());
}
}
//3
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
if (newDest != null) {
...略
//1
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
mLifecycleOwner, mViewModel);
mBackStack.addFirst(entry);
}
//2
// And finally, add the new destination with its default args
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
mBackStack.add(newBackStackEntry);
} else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
launchSingleTop = true;
NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
if (singleTopBackStackEntry != null) {
singleTopBackStackEntry.replaceArguments(finalArgs);
}
}
updateOnBackPressedCallbackEnabled();
if (popped || newDest != null || launchSingleTop) {
dispatchOnDestinationChanged();
}
}
- mGraph必须在导航后加入回退栈mBackStack,如果回退栈为空,那么mGraph一定是第一个添加的元素
- 把新的目标NavDestination也加入进回退栈
经过这里,导航具有了返回栈的能力。
继续看3.
根据参数得知,navigator为NavGraphNavigator。
NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras);
newDest为NavGraph。navigator.navigate,会调用到NavGraphNavigator中:
跟踪到下面方法:
```
NavGraphNavigator:
@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());
}
//startDestination可能是ActivityNavigator DialogFragmentNavigator FragmentNavigator NavGraphNavigator
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);
}
```
int startId = destination.getStartDestination();
找到节点的startId,如果没配置。抛出异常。找到id对应的节点,找不到抛出异常。
此时设置的首页节点可能是<activity><fragment><dialog>avigator.navigate()然后继续开始导航
总结:
- 如果没配置startId。抛出异常
- 找不到对应的NavDestination,抛出异常
上面就是启动首页第一个页面的导航路由过程,下面路由分到<activity><fragment><dialog>中
ActivityNavigator#navigate()
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (destination.getIntent() == null) {
throw new IllegalStateException("Destination " + destination.getId()
+ " does not have an Intent set.");
}
//1
Intent intent = new Intent(destination.getIntent());
if (args != null) {
intent.putExtras(args);
String dataPattern = destination.getDataPattern();
if (!TextUtils.isEmpty(dataPattern)) {
// Fill in the data pattern with the args to build a valid URI
StringBuffer data = new StringBuffer();
Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
Matcher matcher = fillInPattern.matcher(dataPattern);
while (matcher.find()) {
String argName = matcher.group(1);
if (args.containsKey(argName)) {
matcher.appendReplacement(data, "");
//noinspection ConstantConditions
data.append(Uri.encode(args.get(argName).toString()));
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ args + " to fill data pattern " + dataPattern);
}
}
matcher.appendTail(data);
intent.setData(Uri.parse(data.toString()));
}
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
intent.addFlags(extras.getFlags());
}
//2
if (!(mContext instanceof Activity)) {
// If we're not launching from an Activity context we have to launch in a new task.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
if (mHostActivity != null) {
final Intent hostIntent = mHostActivity.getIntent();
if (hostIntent != null) {
final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0);
if (hostCurrentId != 0) {
intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId);
}
}
}
//3
final int destId = destination.getId();
intent.putExtra(EXTRA_NAV_CURRENT, destId);
Resources resources = getContext().getResources();
if (navOptions != null) {
int popEnterAnim = navOptions.getPopEnterAnim();
int popExitAnim = navOptions.getPopExitAnim();
if ((popEnterAnim > 0
&& resources.getResourceTypeName(popEnterAnim).equals("animator"))
|| (popExitAnim > 0
&& resources.getResourceTypeName(popExitAnim).equals("animator"))) {
Log.w(LOG_TAG, "Activity destinations do not support Animator resource. Ignoring "
+ "popEnter resource " + resources.getResourceName(popEnterAnim) + " and "
+ "popExit resource " + resources.getResourceName(popExitAnim) + "when "
+ "launching " + destination);
} else {
// For use in applyPopAnimationsToPendingTransition()
intent.putExtra(EXTRA_POP_ENTER_ANIM, popEnterAnim);
intent.putExtra(EXTRA_POP_EXIT_ANIM, popExitAnim);
}
}
//4
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
ActivityOptionsCompat activityOptions = extras.getActivityOptions();
if (activityOptions != null) {
ActivityCompat.startActivity(mContext, intent, activityOptions.toBundle());
} else {
mContext.startActivity(intent);
}
} else {
mContext.startActivity(intent);
}
if (navOptions != null && mHostActivity != null) {
int enterAnim = navOptions.getEnterAnim();
int exitAnim = navOptions.getExitAnim();
if ((enterAnim > 0 && resources.getResourceTypeName(enterAnim).equals("animator"))
|| (exitAnim > 0
&& resources.getResourceTypeName(exitAnim).equals("animator"))) {
Log.w(LOG_TAG, "Activity destinations do not support Animator resource. "
+ "Ignoring " + "enter resource " + resources.getResourceName(enterAnim)
+ " and exit resource " + resources.getResourceName(exitAnim) + "when "
+ "launching " + destination);
} else if (enterAnim >= 0 || exitAnim >= 0) {
enterAnim = Math.max(enterAnim, 0);
exitAnim = Math.max(exitAnim, 0);
mHostActivity.overridePendingTransition(enterAnim, exitAnim);
}
}
// You can't pop the back stack from the caller of a new Activity,
// so we don't add this navigator to the controller's back stack
return null;
}
- 创建跳转Intent
- 如果不是通过mContext启动(其他进程或应用,例如deeplink)设置FLAG_ACTIVITY_NEW_TASK 设置xml中
app:launchSingleTop="true"
则,FLAG_ACTIVITY_SINGLE_TOP - 设置跳转动画
- startActivity(intent)跳转
FragmentNavigator#navigate()
@Nullable
@Override
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;
}
//1
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
//2
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);
}
//3
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);
//4
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
@Deprecated
@NonNull
public Fragment instantiateFragment(@NonNull Context context,
@NonNull FragmentManager fragmentManager,
@NonNull String className, @SuppressWarnings("unused") @Nullable Bundle args) {
return fragmentManager.getFragmentFactory().instantiate(
context.getClassLoader(), className);
}
- 利用全类名,调用instantiateFragment()反射获得Fragment实例
- 设置动画效果
- 创建FragmentTransaction事务,用ft.replace展示
- 提交
ft.replace()会重建View(验证onDestroyView->onCreateView,并不会走到onDestroy),保留实体。
如果我们想以,shou,hide方式该怎么做?
DialogFragmentNavigator#navigate()
DialogFragmentNavigator
@Nullable
@Override
public NavDestination navigate(@NonNull final 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 = mFragmentManager.getFragmentFactory().instantiate(
mContext.getClassLoader(), className);
if (!DialogFragment.class.isAssignableFrom(frag.getClass())) {
throw new IllegalArgumentException("Dialog destination " + destination.getClassName()
+ " is not an instance of DialogFragment");
}
final DialogFragment dialogFragment = (DialogFragment) frag;
dialogFragment.setArguments(args);
dialogFragment.getLifecycle().addObserver(mObserver);
dialogFragment.show(mFragmentManager, DIALOG_TAG + mDialogCount++);
return destination;
}
dialogFragment.show()he dismiss()显示隐藏,
整体流程和源码分析结束。总结一下几点:
- Navigator.Name 是个注解类,他会用在所有Navigator所有子类的类头,用来标记 子类是什么类型的Navigator
- 自定义自己的Navigator,必须继承Navigator,并且在类头上定义自己的Navigator.Name
- 自定义的Navigator,必须加入NavigatorProvider#mNavigators这个Map中注册Navigator.Name的value就是Map的key
-
NavHostController
这个类没啥实际作用,就是为了和NavHostFragment形式上同样,真正的实现都在父类NavController中 - 有需求要拦截返回键,做我们想做的事情,可以像dispatcher注册我们自己的监听回调。
- Navigation路由跳转的容器必须是NavHostFragment,否则无法支持Dialog和Fragment跳转能力
- 如果自定义FragmentNavigator和DialogFragmentNavigator类型,传入
的FragmentManager
是getChildFragmentManager()
- 所有NavDestination都要存入NavGraph#mNodes
- 如果没配置startId。抛出异常,找不到对应的NavDestination,抛出异常
Navigation 优缺点
优点:
支持Activity,Fragment,Dialog跳转
safesArgs安全数据传输
允许自定义导航行为
支持Deeplink
可视化编辑页面
回退栈管理
Android组件(如:BottomNavigationView)完美交互,JetPack其他组件联合使用
缺点:
所有节点定义在nav_graph.xml不方便管理,灵活性较差
Fragment切换时用replace()销货视图,重新绑定数据
下篇将对Navigation进行实战改造去除店xml文件,利用json文件+注解形式,动态生成路由文件和管理路由配置
网友评论