Android Jetpack之Navigation源码分析

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执行。

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

在这里插入图片描述
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容