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

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

相关推荐
alexhilton5 天前
Android技巧:学习使用GridLayout
android·kotlin·android jetpack
Wgllss13 天前
轻松搞定Android蓝牙打印机,双屏异显及副屏分辨率适配解决办法
android·架构·android jetpack
alexhilton19 天前
群星闪耀的大前端开发
android·kotlin·android jetpack
一航jason1 个月前
Android Jetpack Compose 现有Java老项目集成使用compose开发
android·java·android jetpack
帅次1 个月前
Android CoordinatorLayout:打造高效交互界面的利器
android·gradle·android studio·rxjava·android jetpack·androidx·appcompat
IAM四十二1 个月前
Jetpack Compose State 你用对了吗?
android·android jetpack·composer
Wgllss1 个月前
那些大厂架构师是怎样封装网络请求的?
android·架构·android jetpack
x0242 个月前
Android Room(SQLite) too many SQL variables异常
sqlite·安卓·android jetpack·1024程序员节
alexhilton2 个月前
深入理解观察者模式
android·kotlin·android jetpack
Wgllss2 个月前
花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
android·性能优化·android jetpack