美文网首页Android Studio
Android Jetpack之Navigation源码分析

Android Jetpack之Navigation源码分析

作者: 无名长空剑_real | 来源:发表于2019-08-15 18:07 被阅读0次

    Android Jetpack之Navigation源码分析

    Android Navigation简介

    关于Fragment的基础篇:Fragment基础篇
    官方指导地址:官方指地址
    Github demo 地址:demo
    使用Navigation可以管理APP页面跳转。Navigation不部分情况下作用于Fragment中,使用Navigation切换Fragment可以使代码简洁,直观。Navigation导航组件还支持:Fragment、Activity、导航图和子图、自定义目标等。

    Navigation的使用

    基础使用

    1. 添加项目组件依赖
     def nav_dep = "2.0.0"
      
     implementation "androidx.navigation:navigation-fragment:$nav_dep"
     implementation "androidx.navigation:navigation-ui:$nav_dep"
    
    1. 导航文件XML
      在module下的res目录下,新建navigation文件夹,然后在navigation文件夹下新建一个navigation的xml文件:navigation_jetpack.xml
    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:tools="http://schemas.android.com/tools"
                app:startDestination="@id/first_fragment">
    
        <fragment
                android:id="@+id/first_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.FirstNavigationFragment"
                android:label="first_fragment"
                tools:layout="@layout/fragment_first_navigation">
            <action
                    android:id="@+id/action_to_second_fragment"
                    app:destination="@id/second_fragment" />
    
        </fragment>
    
        <fragment
                android:id="@+id/second_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.SecondNavigationFragment"
                android:label="second_fragment"
                tools:layout="@layout/fragment_second_navigation">
            <action
                    android:id="@+id/action_popup_to_first_fragment"
                    app:popUpTo="@id/first_fragment" />
            <action
                    android:id="@+id/action_to_third_fragment"
                    app:destination="@id/third_fragment" />
            <argument
                    android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
            />
        </fragment>
    
        <fragment
                android:id="@+id/third_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.ThirdNavigationFragment"
                android:label="third_navigation"
                tools:layout="@layout/fragment_third_navigation" >
            <action android:id="@+id/action_popup_to_first_fragment_from_third"
                    app:popUpTo="@id/first_fragment"/>
        </fragment>
    </navigation>
    

    字段解析:
    (1)navigation根节点 startDestination 表示第一个显示的fragment。即FirstNavigationFragment
    (2)fragment 节点中name属性表示所属的fragment类
    (3)fragment 节点中action节点destination属性用于指定下一个目标fragment
    (4)fragment 节点中argument 用于传递数据。表示的是传递到当前Fragment的数据,Key为name属性,默认数据是android:defaultValue,数据类型是argType。

    1. 创建Fragment
      以ThirdNavigationFragment为例。onCreateView返回布局View。onViewCreated设置点击时间,执行相应的action,来完成Fragment的跳转。在页面跳欢时,会执行onDestroyView方法,从新回到该Fragment,会执行方法onCreateView方法。
    class ThirdNavigationFragment : Fragment(){
        override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
            return LayoutInflater.from(this.activity).inflate(R.layout.fragment_third_navigation,container,false)
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            btn_third_fragment.setOnClickListener{
                Navigation.findNavController(it).navigate(R.id.action_popup_to_first_fragment_from_third)
            }
        }
    }
    
    1. 创建Activity
      4.1 activity布局文件。
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        <!--xml实现-->
        <fragment
                android:id="@+id/fragment_navigation"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:name="androidx.navigation.fragment.NavHostFragment"
                app:defaultNavHost="false"
                app:navGraph="@navigation/navigation_jetpack"/>
        <!--代码实现-->
        <FrameLayout
                android:id="@+id/ll_fragment_navigation"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
        ></FrameLayout>
    </LinearLayout>
    

    布局解析:
    (1)xml实现和代码实现在使用时,请务必注解其中一个。
    (2)如果使用xml实现,fragment务必设置id。navGraph 用来表示上面的导航意图文件 navigation_jetpack.xml
    (3)name 必须指定为以下值,这是切换fragment的容器
    android:name="androidx.navigation.fragment.NavHostFragment"
    (4)defaultNavHost 表示是否拦截返回键,默认为false。
    4.2 Activity中使用
    如果使用的是代码实现的布局文件,在Activity中使用如下代码:
    (1)初始化NavHostFragment。
    (2)将NavHostFragment绑定到布局文件的FrameLayout中。

    class NavigationActivity : AppCompatActivity(){
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_navigation)
    
            val finalHost = NavHostFragment.create(R.navigation.navigation_jetpack)
    
            supportFragmentManager.beginTransaction()
                .replace(R.id.ll_fragment_navigation, finalHost)
                .setPrimaryNavigationFragment(finalHost)
                .commit()
        }
    }
    
    1. 切换fragment

    切换Fragment主要有一下两种方式:
    (1)方式1:

    Navigation.findNavController(it).navigate(R.id.action_to_second_fragment)
    

    (2)方式2:

    NavHostFragment.findNavController(this).navigate(R.id.action_to_second_fragment,null)
    

    这两种实现方式其实都返回了一个 NavController 类,然后再通过调用navigate方法控制页面导航,也就是说通过NavController 我们可以控制所有的Fragment导航行为。

    1. 数据传递
      在navigation导航文件中可以通过设置argument标签,来设置fragment所接收的参数类型和默认值。
     <argument
           android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
            />
    

    或者在代码中使用传统方式:

     val bundle = Bundle()
     bundle.putString("name","Blank")
     bundle.putInt("number",10)
     NavHostFragment.findNavController(this).navigate(R.id.action_to_second_fragment,bundle)
    

    在代码中,使用navigate() 方法并将Bundle并将其传递到目标。接受方Fragment中,使用getArguments()方法检索包并使用其内容。

    1. 嵌套导航图
      可以将目的地分组为导航图中的子图,子图也被称为“ 嵌套图 ”,包含图称为“ 根图“。如下我们建立子图:third_navigation。
    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:tools="http://schemas.android.com/tools"
                app:startDestination="@id/first_fragment">
    
        <fragment
                android:id="@+id/first_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.FirstNavigationFragment"
                android:label="first_fragment"
                tools:layout="@layout/fragment_first_navigation">
            <action
                    android:id="@+id/action_to_second_fragment"
                    app:destination="@id/second_fragment" />
    
        </fragment>
    
        <fragment
                android:id="@+id/second_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.SecondNavigationFragment"
                android:label="second_fragment"
                tools:layout="@layout/fragment_second_navigation">
            <action
                    android:id="@+id/action_popup_to_first_fragment"
                    app:popUpTo="@id/first_fragment" />
            <action
                    android:id="@+id/action_to_third_fragment"
                    app:destination="@id/third_navigation" />
            <argument
                    android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
            />
        </fragment>
    
        <navigation
                android:id="@+id/third_navigation"
                app:startDestination="@id/third_fragment">
            <fragment
                    android:id="@+id/third_fragment"
                    android:name="com.jandroid.module_common.jetpack.navigation.ThirdNavigationFragment"
                    android:label="third_navigation"
                    tools:layout="@layout/fragment_third_navigation" >
                <action android:id="@+id/action_popup_to_first_fragment_from_third"
                        app:popUpTo="@id/first_fragment"/>
            </fragment>
        </navigation>
    </navigation>
    
    1. include引用其他图形
      使用include引用其他图形
      比如我们建立视图:navigation_nested.xml 布局文件如下:
    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:tools="http://schemas.android.com/tools"
                android:id="@+id/navigation_nested"
                app:startDestination="@id/third_fragment">
    
        <fragment
                android:id="@+id/third_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.ThirdNavigationFragment"
                android:label="third_navigation"
                tools:layout="@layout/fragment_third_navigation" >
            <action android:id="@+id/action_popup_to_first_fragment_from_third"
                    app:popUpTo="@id/first_fragment"/>
        </fragment>
    
    </navigation>
    

    在navigation_jetpack中include navigation_nested。

    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                xmlns:tools="http://schemas.android.com/tools"
                app:startDestination="@id/first_fragment">
    
        <fragment
                android:id="@+id/first_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.FirstNavigationFragment"
                android:label="first_fragment"
                tools:layout="@layout/fragment_first_navigation">
            <action
                    android:id="@+id/action_to_second_fragment"
                    app:destination="@id/second_fragment" />
    
        </fragment>
    
        <fragment
                android:id="@+id/second_fragment"
                android:name="com.jandroid.module_common.jetpack.navigation.SecondNavigationFragment"
                android:label="second_fragment"
                tools:layout="@layout/fragment_second_navigation">
            <action
                    android:id="@+id/action_popup_to_first_fragment"
                    app:popUpTo="@id/first_fragment" />
            <action
                    android:id="@+id/action_to_third_fragment"
                    app:destination="@id/navigation_nested" />
            <argument
                    android:name="title" app:argType="string" android:defaultValue="jetpack navigation"
            />
        </fragment>
        <include  app:graph="@navigation/navigation_nested"/>
    </navigation>
    
    1. 其他用法

    Navigation的其他用法可以参考下面Blog,包含Deep Link等。

    https://cloud.tencent.com/developer/article/1446342

    https://blog.csdn.net/lyhhj/article/details/93757755

    https://cloud.tencent.com/developer/article/1452921

    源码分析

    3.1 构建NavController

    通过上面的Navigation使用,我们知道NavHostFragment作为一个容器,所有的导航操作都是NavHostFragment中进行,在NavHostFragment中又委托给了NavController类。所以我们下面主要看看NavController类是如何被创建出来的,以及在创建过程中NavHostFragment类都做那些初始化工作。NavHostFragment的初始化主要有两种实现方式,1:配置XML文件。2:代码实现。下面我们以代码实现NavHostFragment.create为入口来分析,NavHostFragment类。

      val finalHost = NavHostFragment.create(R.navigation.navigation_jetpack)
    
            supportFragmentManager.beginTransaction()
                .replace(R.id.ll_fragment_navigation, finalHost)
                .setPrimaryNavigationFragment(finalHost)
                .commit()
    
    1. NavHostFragment.create方法
      (1)初始化Bundle,并且将graphResId,startDestinationArgs存储在Bundle中。
      (2)返回NavHostFragment实例。
    public static NavHostFragment create(@NavigationRes int graphResId,
                @Nullable Bundle startDestinationArgs) {
            Bundle b = null;
            if (graphResId != 0) {
                b = new Bundle();
                b.putInt(KEY_GRAPH_ID, graphResId);
            }
            if (startDestinationArgs != null) {
                if (b == null) {
                    b = new Bundle();
                }
                b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
            }
    
            final NavHostFragment result = new NavHostFragment();
            if (b != null) {
                result.setArguments(b);
            }
            return result;
        }
    
    1. NavHostFragment.onInflate方法
      当Fragment以XML的方式静态加载时,最先会调用onInflate的方法(调用时机:Fragment所关联的Activity在执行setContentView时)。
      (1)主要是解析布局文件的两个属性。defaultNavHost和navGraph,并且初始化全局变量
      (1)defaltNavHost为true时,NavHostFragment将会通过FragmentManager 切换到回退栈顶部,并且可以拦截返回键事件(back事件)。
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
                @Nullable Bundle savedInstanceState) {
            super.onInflate(context, attrs, savedInstanceState);
    
            final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
            final int graphId = a.getResourceId(R.styleable.NavHostFragment_navGraph, 0);
            final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
    
            if (graphId != 0) {
                mGraphId = graphId;
            }
            if (defaultHost) {
                mDefaultNavHost = true;
            }
            a.recycle();
        }
    
    1. NavHostFragment.onCreate方法。无论是XML实现还是代码实现,都会执行Fragment的onCreate方法,可谓是殊途同归。NavController在这里被创建,并且NavHostFragment中有一个NavController对象。
      (1)初始化NavController,NavController为导航的控制类,核心类。
      (2)在SimpleNavigatorProvider中以键值对保存FragmentNavigator类。该类之后会做介绍。
      (3)savedInstanceState不为空时候,恢复controller的状态
      (4)将graph设置给navController,构建NavGraph。下面会单独分析该模块。
      (5)当defaltNavHost为true,将会被设置为主导航fragment。可以拦截返回键事件(back事件)。
      (6)通过addNavigator添加FragmentNavigator,下面会分析到。
    public void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            final Context context = requireContext();
    
            mNavController = new NavController(context);
            mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
    
            Bundle navState = null;
            if (savedInstanceState != null) {
                navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
                if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                    mDefaultNavHost = true;
                    requireFragmentManager().beginTransaction()
                            .setPrimaryNavigationFragment(this)
                            .commit();
                }
            }
    
            if (navState != null) {
                // Navigation controller state overrides arguments
                mNavController.restoreState(navState);
            }
            if (mGraphId != 0) {
                // 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. NavController.onCreateView方法
      该NavHostFragment的视图就只有一个FrameLayout布局, 在NavHostFragment的创建时,为它创建一个FrameLayout作为导航界面的载体。
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                                 @Nullable Bundle savedInstanceState) {
            FrameLayout frameLayout = new FrameLayout(inflater.getContext());
            // When added via XML, this has no effect (since this FrameLayout is given the ID
            // automatically), but this ensures that the View exists as part of this Fragment's View
            // hierarchy in cases where the NavHostFragment is added programmatically as is required
            // for child fragment transactions
            frameLayout.setId(getId());
            return frameLayout;
        }
    
    1. NavController.onViewCreated
      (1)当通过XML添加时,父View是null,我们的view就是NavHostFragment的根。
      (2)但是当以代码方式添加时,需要在父级上设置NavController。
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            if (!(view instanceof ViewGroup)) {
                throw new IllegalStateException("created host view " + view + " is not a ViewGroup");
            }
            // When added via XML, the parent is null and our view is the root of the NavHostFragment
            // but when added programmatically, we need to set the NavController on the parent - i.e.,
            // the View that has the ID matching this NavHostFragment.
            View rootView = view.getParent() != null ? (View) view.getParent() : view;
            Navigation.setViewNavController(rootView, mNavController);
        }
    
    1. Navigation.setViewNavController方法。
      主要是将NavController对象设置为rootView的tag。方便以后递归遍历到NavController对象,确保NavController对象的唯一。
      public static void setViewNavController(@NonNull View view,
                @Nullable NavController controller) {
            view.setTag(R.id.nav_controller_view_tag, controller);
        }
    

    至此整个NavController对象和NavHostFragment的关系我们已经梳理明白了。下面来看看
    NavController是如何参与到导航事件的。

    3.2 获取NavController

    要想NavController参与到导航事件,必须获取到该对象才可以。在Fragment 中控制导航的时候,上面介绍了两种实现方式。
    (1)Navigation.findNavController(it).navigate(R.id.action_page)
    (2)NavHostFragment.findNavController(this).navigate(R.id.action)
    其实无论是通过findNavController或者是findNavController返回的都是NavController对象。
    在构建NavController对象的时候,我们使用到了Navigation类,下面就从该类分析。findNavController方法形参是个View对象,所以是通过view就去查找就NavController,还记得上面用到的viewRoot吗?。

    1. findNavController方法
      该方法没什么实质性的代码,只要是调用了findViewNavController方法。
       public static NavController findNavController(@NonNull View view) {
            NavController navController = findViewNavController(view);
            if (navController == null) {
                throw new IllegalStateException("View " + view + " does not have a NavController set");
            }
            return navController;
        }
    
    1. findViewNavController方法
      通过view递归循环查找NavController。内部调用了getViewNavController方法。
     private static NavController findViewNavController(@NonNull View view) {
            while (view != null) {
                NavController controller = getViewNavController(view);
                if (controller != null) {
                    return controller;
                }
                ViewParent parent = view.getParent();
                view = parent instanceof View ? (View) parent : null;
            }
            return null;
        }
    
    1. getViewNavController方法
      通过获取view的Tag,获取NavController对象,这里的tag ID和setViewNavController都是nav_controller_view_tag。
    private static NavController getViewNavController(@NonNull View view) {
            Object tag = view.getTag(R.id.nav_controller_view_tag);
            NavController controller = null;
            if (tag instanceof WeakReference) {
                controller = ((WeakReference<NavController>) tag).get();
            } else if (tag instanceof NavController) {
                controller = (NavController) tag;
            }
            return controller;
        }
    

    至此NavController的获取过程已经分析完毕。

    4 真正的导航实现

    在实现导航的时候,我们需要根据navigation配置文件生成NavGraph类,然后在根据每个不同的action id,找到对应的NavDestination就可以实现页面导航跳转了。

    4.1 构建NavGraph

    1. SimpleNavigatorProvider类
      在构建NavController的时候,在onCreate方法中调用了如下代码。
      mNavController = new NavController(context);
      mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
    

    (1)其中mNavigatorProvider是NavController中的全局变量,内部通过HashMap键值对的形式保存Navigator类。

    private final NavigatorProvider mNavigatorProvider = new NavigatorProvider() {
            @Nullable
            @Override
            public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
                    @NonNull Navigator<? extends NavDestination> navigator) {
                Navigator<? extends NavDestination> previousNavigator =
                        super.addNavigator(name, navigator);
                if (previousNavigator != navigator) {
                    if (previousNavigator != null) {
                        previousNavigator.removeOnNavigatorBackPressListener(mOnBackPressListener);
                    }
                    navigator.addOnNavigatorBackPressListener(mOnBackPressListener);
                }
                return previousNavigator;
            }
        };
    

    (2)createFragmentNavigator方法,构建了FragmentNavigator对象,其中抽象类Navigator还有个重要的实现类ActivityNavigator和NavGraphNavigator。这个两个类的对象在NavController的构造方法中被添加。。

    public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> 
    

    (3)其中Navigator类的作用是:能够实例化对应的NavDestination,并且能够实现导航功能,拥有自己的回退栈。

    1. 构建NavGraph
      在构建NavController的时候,我们还调用了NavController.setGraph(graphId)方法,该方法主要是构建NavGraph。
      (1)调用getNavInflater方法创建NavInflater对象,用于解析navigation xml文件
      public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
            setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
        }
    

    (2) NavInflater.inflate方法
    根据传入的XML资源id构建NavGraph,NavGraph组成Fragment路由的导航地图,而NavDestination代表了导航的每一个目的地。在解析完NavDestination后,需要要求NavDestination为NavGraph,即NavGraph是NavDestination的子类。而且在NavGraph内部存储了NavDestination信息。

    public NavGraph inflate(@NavigationRes int graphResId) {
            Resources res = mContext.getResources();
            //拿到XML的解析器
            XmlResourceParser parser = res.getXml(graphResId);
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            try {
                String rootElement = parser.getName();
                //构建出NavDestination
                NavDestination destination = inflate(res, parser, attrs, graphResId);
                //合法性检测
                if (!(destination instanceof NavGraph)) {
                    throw new IllegalArgumentException("Root element <" + rootElement + ">"
                            + " did not inflate into a NavGraph");
                }
                return (NavGraph) destination;
            } catch (Exception e) {
                throw new RuntimeException("Exception inflating "
                        + res.getResourceName(graphResId) + " line "
                        + parser.getLineNumber(), e);
            } finally {
                parser.close();
            }
        }
    
    

    上面的inflate方法内部会继续调用inflate方法。
    (1)getNavigator方法获取都Navigator实例,该实例在构建NavController是被添加进去,这里获取的是FragmentNavigator对象。
    (2)createDestination方法,会调用FragmentNavigator的createDestination构建Destination对象。
    (3)onInflate方法,调用FragmentNavigator.Destination的方法获取设置的Fragment的类名。
    (4)while循环内部通过递归构建导航图。

    private NavDestination inflate(Resources res, XmlResourceParser parser, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        Navigator navigator = mNavigatorProvider.getNavigator(parser.getName());
        final NavDestination dest = navigator.createDestination();
        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)) {
                //解析参数,存储在dest中
                inflateArgument(res, dest, attrs);
            } else if (TAG_DEEP_LINK.equals(name)) {
                //解析深度链接
                inflateDeepLink(res, dest, attrs);
            } else if (TAG_ACTION.equals(name)) {
                //解析Action
                inflateAction(res, dest, attrs);
            } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
                //如果子节点为graph,加载子节点的destination。即通过include方法。
                final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavInclude);
                final int id = a.getResourceId(R.styleable.NavInclude_graph, 0);
                ((NavGraph) dest).addDestination(inflate(id));
                a.recycle();
            } else if (dest instanceof NavGraph) {
                //如果子节点为graph加载子节点的destination
                //向每个NavGraph中加入Destination
                ((NavGraph) dest).addDestination(inflate(res, parser, attrs));
            }
        }
        return dest;
    }
    
    1. onGraphCreated方法。
      通过NavInflater类之后,解析了XML文件构建整个Graph之后。,下面回到setGraph方法,在解析玩XML后会调用setGraph方法。
      (1)popBackStackInternal方法将旧的导航图全部出栈。
      (2)调用onGraphCreated主要是显示一个导航Fragment视图。
     public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
            if (mGraph != null) {
                // Pop everything from the old graph off the back stack
                popBackStackInternal(mGraph.getId(), true);
            }
            mGraph = graph;
            onGraphCreated(startDestinationArgs);
        }
    
    1. onGraphCreated方法
      (1)恢复之前的导航状态
      (2)调用navigate方法,显示第一个Fragment。即在Navigation文件里,属性app:startDestination的Fragment。所以最终都会走到navigate导航方法。
     private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
            if (mNavigatorStateToRestore != null) {
                ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
                        KEY_NAVIGATOR_STATE_NAMES);
                if (navigatorNames != null) {
                    for (String name : navigatorNames) {
                        Navigator navigator = mNavigatorProvider.getNavigator(name);
                        Bundle bundle = mNavigatorStateToRestore.getBundle(name);
                        if (bundle != null) {
                            navigator.onRestoreState(bundle);
                        }
                    }
                }
            }
            if (mBackStackIdsToRestore != null) {
                for (int index = 0; index < mBackStackIdsToRestore.length; index++) {
                    int destinationId = mBackStackIdsToRestore[index];
                    Bundle args = (Bundle) mBackStackArgsToRestore[index];
                    NavDestination node = findDestination(destinationId);
                    if (node == null) {
                        throw new IllegalStateException("unknown destination during restore: "
                                + mContext.getResources().getResourceName(destinationId));
                    }
                    if (args != null) {
                        args.setClassLoader(mContext.getClassLoader());
                    }
                    mBackStack.add(new NavBackStackEntry(node, args));
                }
                mBackStackIdsToRestore = null;
                mBackStackArgsToRestore = null;
            }
            if (mGraph != null && mBackStack.isEmpty()) {
                boolean deepLinked = 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);
                }
            }
        }
    
    

    导航

    在构建和获取到NavController对象以及NavGraph之后。,下面是使用它来实现真正的导航了。下面从navigate开始分析。在navigate方法内部会查询到NavDestination,然后根据不同的Navigator实现页面导航。

    1. navigate 方法
      (1)如果回退栈为null返回NavGraph,不为null返回回退栈中的最后一项。
      (2)根据id,获取对应的NavAction。然后在通过NavAction获取目的地id。
      (4)利用目的地ID属性,通过findDestination方法,找到准备导航的目的地。
      (5)根据导航目的地的名字,调用getNavigator方法,获取Navigator对象。这里对应的是FragmentNavigator。
     public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
                @Nullable Navigator.Extras navigatorExtras) {
            NavDestination currentNode = mBackStack.isEmpty()
                    ? mGraph
                    : mBackStack.getLast().getDestination();
            if (currentNode == null) {
                throw new IllegalStateException("no current navigation node");
            }
            @IdRes int destId = resId;
            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 (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) {
                popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
                return;
            }
            NavDestination node = findDestination(destId);
            navigate(node, combinedArgs, navOptions, navigatorExtras);
        }
    
    private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
                @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
            boolean popped = false;
            if (navOptions != null) {
                if (navOptions.getPopUpTo() != -1) {
                    popped = popBackStackInternal(navOptions.getPopUpTo(),
                            navOptions.isPopUpToInclusive());
                }
            }
            Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                    node.getNavigatorName());
            Bundle finalArgs = node.addInDefaultArgs(args);
            NavDestination newDest = navigator.navigate(node, finalArgs,
                    navOptions, navigatorExtras);
         
    
    1. FragmentNavigator的实现
      通过以上的分析,又来到了Navigator 的子类FragmentNavigator类。下面来看看FragmentNavigator.navigate的方法。
      (1)调用instantiateFragment,通过反射机制构建Fragment实例
      (2)处理进出场等动画逻辑
      (3)最终调用FragmentManager来处理导航逻辑。
      猜测ActivityNavigator最终也是调用了startActivity方法,这里就不展示代码了。
     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;
            }
            final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                    className, args);
            frag.setArguments(args);
            final FragmentTransaction ft = mFragmentManager.beginTransaction();
    
            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);
            }
    
            ft.replace(mContainerId, frag);
            ft.setPrimaryNavigationFragment(frag);
            ft.setReorderingAllowed(true);
            ft.commit();
        }
    

    小结

    (1)NavHostFragment 作为导航载体,在Activity的layout文件里被引用(或者在代码中动态),并且持有导航控制类NavController引用。
    (2)NavController 将导航任务委托给Navigator类,Navigator类有两个重要的子类FragmentNavigator和ActivityNavigator子类。NavController类持有NavInflater类引用。
    (3)NavInflater 负责解析Navgation文件,负责构建NavGraph导航图。
    (4)NavDestination 存有各个目的地信息,在FragmentNavigator和ActivityNavigator内部分别对应一个Destination类,该类继承NavDestination。
    (5)在页面导航时,fragment的操作还是交由FragmentManager在操作,activity交由startActivity执行。

    下面贴一下,网上总结的比较全的类图信息:
    来自图片来源

    在这里插入图片描述

    相关文章

      网友评论

        本文标题:Android Jetpack之Navigation源码分析

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