美文网首页
Navigation深入浅出,到出神入化,再到实战改造(二)

Navigation深入浅出,到出神入化,再到实战改造(二)

作者: g小志 | 来源:发表于2022-03-09 01:37 被阅读0次

    了解Navigation使用后,思考几个问题

    1. NavHostFragmnet作为路由容器,是如何解析nav_graph资源文件,从而生成NavGraph对象?
    2. 跳转时,路由是如何被执行的?
    3. 跳转的路由目标节点,NavDestination又是如何创建的。
    4. 分析后是否能总结出Navigation的优点和痛点
    5. 能否解决痛点,该如何解决,有什么思路?

    源码分析从下面的图入手:

    版本:
    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 很重要

    以上第一部分完成。总结如下:

    1. NavHostController 这个类没啥实际作用,就是为了和NavHostFragment形式上同样,真正的实现都在父类NavController中
    2. 想要自定义自己的Navigator,必须继承Navigator,并且在类头上定义自己的Navigator.Name。
    3. 自定义的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(); 出栈方法

    总结:

    1. 给我们个提示,如果我们有需求要拦截返回键,做我们想做的事情,可以像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中标跳转的能力

    总结:

    1. Navigation路由跳转的容器必须是NavHostFragment,否则无法支持Dialog和Fragment跳转能力
    2. 如果自定义FragmentNavigator和DialogFragmentNavigator类型,传入的FragmentManagergetChildFragmentManager()

    此时回头看往上看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();
            }
        }
    
    1. mGraph必须在导航后加入回退栈mBackStack,如果回退栈为空,那么mGraph一定是第一个添加的元素
    2. 把新的目标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()然后继续开始导航

    总结:

    1. 如果没配置startId。抛出异常
    2. 找不到对应的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;
        }
    
    1. 创建跳转Intent
    2. 如果不是通过mContext启动(其他进程或应用,例如deeplink)设置FLAG_ACTIVITY_NEW_TASK 设置xml中app:launchSingleTop="true"则,FLAG_ACTIVITY_SINGLE_TOP
    3. 设置跳转动画
    4. 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);
        }
    
    1. 利用全类名,调用instantiateFragment()反射获得Fragment实例
    2. 设置动画效果
    3. 创建FragmentTransaction事务,用ft.replace展示
    4. 提交

    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()显示隐藏,

    整体流程和源码分析结束。总结一下几点:

    1. Navigator.Name 是个注解类,他会用在所有Navigator所有子类的类头,用来标记 子类是什么类型的Navigator
    2. 自定义自己的Navigator,必须继承Navigator,并且在类头上定义自己的Navigator.Name
    3. 自定义的Navigator,必须加入NavigatorProvider#mNavigators这个Map中注册Navigator.Name的value就是Map的key
    4. NavHostController 这个类没啥实际作用,就是为了和NavHostFragment形式上同样,真正的实现都在父类NavController中
    5. 有需求要拦截返回键,做我们想做的事情,可以像dispatcher注册我们自己的监听回调。
    6. Navigation路由跳转的容器必须是NavHostFragment,否则无法支持Dialog和Fragment跳转能力
    7. 如果自定义FragmentNavigator和DialogFragmentNavigator类型,传入的FragmentManagergetChildFragmentManager()
    8. 所有NavDestination都要存入NavGraph#mNodes
    9. 如果没配置startId。抛出异常,找不到对应的NavDestination,抛出异常

    Navigation 优缺点

    优点:
    支持Activity,Fragment,Dialog跳转
    safesArgs安全数据传输
    允许自定义导航行为
    支持Deeplink
    可视化编辑页面
    回退栈管理
    Android组件(如:BottomNavigationView)完美交互,JetPack其他组件联合使用

    缺点:
    所有节点定义在nav_graph.xml不方便管理,灵活性较差
    Fragment切换时用replace()销货视图,重新绑定数据

    下篇将对Navigation进行实战改造去除店xml文件,利用json文件+注解形式,动态生成路由文件和管理路由配置

    相关文章

      网友评论

          本文标题:Navigation深入浅出,到出神入化,再到实战改造(二)

          本文链接:https://www.haomeiwen.com/subject/iunzrrtx.html