在上一篇文章中讲解了 Navigation 的初始化的过程, 链接在JetPack源码分析之Navigation原理(一), 这里先来回顾一下上一篇文章中 NavHostFragment 的初始化过程
1: 创建 NavHostFragment
创建分为2种方式,使用代码手动创建 调用 NavHostFragment 的create 方法,与在xml中 将 的标签name 指向 NavHostFragment , 接下来就会走他们的默认方法,标记一些属性,是否管理回退栈,以及 graphid 等,
2: 创建 NavController
在onCreate 生命周期方法中创建 NavController,并给他的 provider 设置4个类型,不同种类型的 Navigator 放入Map 中不同的字段里面
3: 解析默认的Graph,管理回退站与默认跳转事件
4: 确保view存在id
这个方法是在onCreateView 中进行的
5: 将view 与 NavController 关联
这个方法是在onViewCreated 中进行的
下面我就来看一下跳转的整个过程是如何进行的,
想要实现跳转, 方法大致如下
kotlin
NavHostFragment.findNavController(this).navigate(R.id.go_to_2)//
那么第一步就需要find,这个过程的操作大致如下
java
@NonNull
public static NavController findNavController(@NonNull Fragment fragment) {
Fragment findFragment = fragment;
/// 寻找 NavHostFragment,如果当前Fragment 不是,就向上继续寻找,直到找到为止
while (findFragment != null) {
if (findFragment instanceof NavHostFragment) {
return ((NavHostFragment) findFragment).getNavController();
}
Fragment primaryNavFragment = findFragment.getParentFragmentManager()
.getPrimaryNavigationFragment();
if (primaryNavFragment instanceof NavHostFragment) {
return ((NavHostFragment) primaryNavFragment).getNavController();
}
findFragment = findFragment.getParentFragment();
}
View view = fragment.getView();
/// 由于在onViewCreated 方法中将view 与 NavController 绑定到了一起,
//所以通过这里能找到,但是Google 在这里还是做了很多兼容,那就是在view查找过程中,
//也与上面fragment 的查找过程类似,是逐级向上的,确保能够找到,由于代码量的关系,
//这里就不说明了
if (view != null) {
return Navigation.findNavController(view);
}
// For DialogFragments, look at the dialog's decor view
Dialog dialog = fragment instanceof DialogFragment
? ((DialogFragment) fragment).getDialog()
: null;
if (dialog != null && dialog.getWindow() != null) {
return Navigation.findNavController(dialog.getWindow().getDecorView());
}
throw new IllegalStateException("Fragment " + fragment
+ " does not have a NavController set");
}
找到了 NavController 接下来就可以愉快的使用navigate(R.id.go_to_2)进行跳转了
在跳转前需要先明确目标,那就是需要找到这个目的地的节点 , 寻找代码如下
less
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
@Nullable Navigator.Extras navigatorExtras) {
//步骤1: 先去回退栈里面查找 NavDestination ,如果回退栈里面有内容,那么就找到回退栈里面最后一个的
//NavDestination,如果没有就返回 默认 graph ,在一般情况下就是 graphid 指向的
NavDestination currentNode = mBackStack.isEmpty()
? mGraph
: mBackStack.getLast().getDestination();
if (currentNode == null) {
throw new IllegalStateException("no current navigation node");
}
@IdRes int destId = resId;
//步骤2:使用参数id 去 步骤1 中查找到的 NavDestination 查找,
//找到了重新赋值目标id,并尝试添加安全参数
final NavAction navAction = currentNode.getAction(resId);
Bundle combinedArgs = null;
if (navAction != null) {
if (navOptions == null) {
navOptions = navAction.getNavOptions();
}
destId = navAction.getDestinationId();
Bundle navActionArgs = navAction.getDefaultArguments();
if (navActionArgs != null) {
combinedArgs = new Bundle();
combinedArgs.putAll(navActionArgs);
}
}
if (args != null) {
if (combinedArgs == null) {
combinedArgs = new Bundle();
}
combinedArgs.putAll(args);
}
//步骤3: 如果目标id==0 ,并且navOptions.getPopUpTo() != -1 ,证明应该是回退事件,那么则执行回退
if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) {
popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
return;
}
if (destId == 0) {
throw new IllegalArgumentException("Destination id == 0 can only be used"
+ " in conjunction with a valid navOptions.popUpTo");
}
//步骤4:去当前的NavGraph 中根据目标id 去 SparseArrayCompat 中查找 NavDestination ,
NavDestination node = findDestination(destId);
if (node == null) {
final String dest = NavDestination.getDisplayName(mContext, destId);
if (navAction != null) {
throw new IllegalArgumentException("Navigation destination " + dest
+ " referenced from action "
+ NavDestination.getDisplayName(mContext, resId)
+ " cannot be found from the current destination " + currentNode);
} else {
throw new IllegalArgumentException("Navigation action/destination " + dest
+ " cannot be found from the current destination " + currentNode);
}
}
//步骤5:根据上面找到id NavDestination进行跳转
navigate(node, combinedArgs, navOptions, navigatorExtras);
}
这个寻找的过程大致分为4步,如下
步骤1: 先去回退栈里面查找 NavDestination ,如果回退栈里面有内容,那么就找到回退栈里面最后一个的 NavDestination,如果没有就返回 默认 graph ,在一般情况下就是 graphid 指向的
步骤2:使用参数id 去 步骤1 中查找到的 NavDestination 查找,找到了重新赋值目标id,并尝试添加安全参数
步骤3: 如果目标id==0 ,并且navOptions.getPopUpTo() != -1 ,证明应该是回退事件,那么则执行回退
步骤4:去当前的NavGraph 中根据目标id 去 SparseArrayCompat 中查找 NavDestination ,
步骤5:根据上面找到id NavDestination进行跳转
接下来继续分析跳转事件
scss
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());
}
}
/// 步骤1: 先根据NavDestination 去 provider 中找到 他是哪种类型的跳转
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
///步骤2:根据不同种类型来执行跳转
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
}
}
// When you navigate() to a NavGraph, we need to ensure that a new instance
// is always created vs reusing an existing copy of that destination
ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
NavDestination destination = newDest;
if (node instanceof NavGraph) {
do {
NavGraph parent = destination.getParent();
if (parent != null) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, parent,
finalArgs, mLifecycleOwner, mViewModel);
hierarchy.addFirst(entry);
// Pop any orphaned copy of that navigation graph off the back stack
if (!mBackStack.isEmpty()
&& mBackStack.getLast().getDestination() == parent) {
popBackStackInternal(parent.getId(), true);
}
}
destination = parent;
} while (destination != null && destination != node);
}
// Now collect the set of all intermediate NavGraphs that need to be put onto
// the back stack
destination = hierarchy.isEmpty()
? newDest
: hierarchy.getFirst().getDestination();
while (destination != null && findDestination(destination.getId()) == null) {
NavGraph parent = destination.getParent();
if (parent != null) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
mLifecycleOwner, mViewModel);
hierarchy.addFirst(entry);
}
destination = parent;
}
NavDestination overlappingDestination = hierarchy.isEmpty()
? newDest
: hierarchy.getLast().getDestination();
// Pop any orphaned navigation graphs that don't connect to the new destinations
//noinspection StatementWithEmptyBody
while (!mBackStack.isEmpty()
&& mBackStack.getLast().getDestination() instanceof NavGraph
&& ((NavGraph) mBackStack.getLast().getDestination()).findNode(
overlappingDestination.getId(), false) == null
&& popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) {
// Keep popping
}
mBackStack.addAll(hierarchy);
// 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);
}
// 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();
}
}
这里跳转的步骤大致为2不
步骤1: 先根据NavDestination 去 provider 中找到他是哪种类型的跳转
步骤2:根据不同种类型来执行跳转
这里我们主要看Fragment 的跳转,其他种类的大家在使用过程中可以自己去查看一下
less
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:反射创建fragment
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
//步骤2:添加参数
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
//步骤3:添加动画
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);
}
// 步骤4:这里是非常重要的一步,在添加过程中使用的是replace , 将原来的移除
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
// 步骤5:可以看到如果是使用deeplinks ,则不能添加回退栈
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;
}
}
fragment 跳转的大致逻辑如下 // 步骤1:反射创建fragment
//步骤2:添加参数 //步骤3:添加动画 // 步骤4:这里是非常重要的一步,在添加过程中使用的是replace , 将原来的移除 // 步骤5:可以看到如果是使用deeplinks ,则不能添加回退栈
梳理这里的逻辑,再去看网上一些大佬发布的图片,就非常好理解了,下面贴一下导图
Jetpack之Navigation使用及源码解读这篇文章博主对于使用说明的很详细,虽然博主没有对源码有太多的说明,但是他的图还是非常好的
到了这里,关于跳转的这个流程就结束了,