JetPack源码分析之Navigation原理(二)

在上一篇文章中讲解了 Navigation 的初始化的过程, 链接在JetPack源码分析之Navigation原理(一), 这里先来回顾一下上一篇文章中 NavHostFragment 的初始化过程

创建分为2种方式,使用代码手动创建 调用 NavHostFragment 的create 方法,与在xml中 将 的标签name 指向 NavHostFragment , 接下来就会走他们的默认方法,标记一些属性,是否管理回退栈,以及 graphid 等,

在onCreate 生命周期方法中创建 NavController,并给他的 provider 设置4个类型,不同种类型的 Navigator 放入Map 中不同的字段里面

3: 解析默认的Graph,管理回退站与默认跳转事件

4: 确保view存在id

这个方法是在onCreateView 中进行的

这个方法是在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使用及源码解读这篇文章博主对于使用说明的很详细,虽然博主没有对源码有太多的说明,但是他的图还是非常好的

到了这里,关于跳转的这个流程就结束了,

相关推荐
帅次4 天前
Android Studio:驱动高效开发的全方位智能平台
android·ide·flutter·kotlin·gradle·android studio·android jetpack
时空掠影1 个月前
Kotlin compose 实现Image 匀速旋转
android·java·开发语言·ios·kotlin·android jetpack·android-studio
白瑞德1 个月前
Android LiveData的使用和原理分析
android·android jetpack
alexhilton1 个月前
降Compose十八掌之『密云不雨』| Navigation
android·kotlin·android jetpack
白瑞德2 个月前
ViewModel的创建、销毁和恢复
android jetpack
张可2 个月前
开源一个 Compose 图片查看框架ImageViewer,支持多种手势。
android·kotlin·android jetpack
bytebeats2 个月前
Jetpack Compose粘性标题的一种可行实现
android·android jetpack
彬_小彬2 个月前
Android mvvm最佳实践
android·android jetpack
alexhilton2 个月前
降Compose十八掌之『鱼跃于渊』| Gesture Handling
android·kotlin·android jetpack
Wgllss2 个月前
注解处理器在架构,框架中实战应用:MVVM中数据源提供Repository类的自动生成
android·架构·android jetpack