(Kotlin版)Navigation的简单使用和源码分析

1.Navigation是什么?


Navigation 是一个框架,用于在 Android 应用中的“目标”之间导航,该框架提供一致的 API,无论目标是作为 Fragment、Activity 还是其他组件实现。

自己的话:

Navigation是管理Fragment之间导航的组件库,特别在实现单个Activity多个Fragment的管理模式更加灵活

其是底部导航栏+Navigation


这里我们使用三个碎片所以先定义三个Fragment

package com.example.myapplication
​
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
​
class MainPage1Fragment :Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main_page1,container,false)
    }
​
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //点击事件跳转到第二个fragment
        val btn = view.findViewById<Button>(R.id.btn)
        btn.setOnClickListener { view ->
            Navigation.findNavController(view).navigate(R.id.action_page2)
        }
    }
}

package com.example.myapplication
​
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
​
class MainPage2Fragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main_page2,container,false)
    }
​
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val btn = view.findViewById<Button>(R.id.btn)
        btn.setOnClickListener { view ->
            Navigation.findNavController(view).navigate(R.id.action_page1)
//             Navigation.findNavController(view).navigateUp(); //返回上一个Fragment,
        }
        val btn2 = view.findViewById<Button>(R.id.btn2)
        btn2.setOnClickListener { view ->
            Navigation.findNavController(view).navigate(R.id.action_page3)
        }
    }
}
package com.example.myapplication
​
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
​
class MainPage3Fragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main_page3,container,false)
    }
​
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val btn = view.findViewById<Button>(R.id.btn)
        btn.setOnClickListener { view ->
            Navigation.findNavController(view).navigate(R.id.action_page2)
            // 回退上一步
            // Navigation.findNavController(view).navigateUp();
        }
    }
}

他们的布局如下

<?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"
xmlns:tools=" http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#00BCD4">

<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MainPage1Fragment"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#3F51B5"
android:layout_gravity="center" />

<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="跳转MainPage2Fragment"
android:textAllCaps="false"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/message"
android:layout_gravity="center"/>

</LinearLayout>
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#3F51B5">
​
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#3F51B5"
        android:text="MainPage2Fragment"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_gravity="center" />
​
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="返回MainPage1Fragment"
        android:textAllCaps="false"
        android:layout_gravity="center" />
​
    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="前往MainPage3Fragment"
        android:textAllCaps="false"
        android:layout_gravity="center"/>
​
</LinearLayout>
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#9C27B0">
​
    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="MainPage3Fragment"
        android:textSize="20sp"
        android:textStyle="bold"
        android:textColor="#3F51B5"
        android:layout_gravity="center" />
​
    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="跳到MainPage2Fragment"
        android:textAllCaps="false"
        android:layout_gravity="center" />
​
</LinearLayout>

布局很简单就是几个Button按钮

它的点击事件就是在我们Navigation工作绑定后进行页面的跳转,这个需要我们在MainActivity里进行绑定,先放在这

现在我们先做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"
    android:id="@+id/nav_graph_main"
    app:startDestination="@id/page1Fragment">
<fragment
    android:id="@+id/page1Fragment"
    android:name="com.example.myapplication.MainPage1Fragment"
    android:label="fragment_page1"
    tools:layout="@layout/fragment_main_page1">
    <!--
          action:程序中使用id跳到destination对应的类
      -->
    <action
        android:id="@+id/action_page2"
        app:destination="@id/page2Fragment" />
</fragment>
    <!-- TODO 同学们 这个是第二个 Fragment -->
    <fragment
        android:id="@+id/page2Fragment"
        android:name="com.example.myapplication.MainPage2Fragment"
        android:label="fragment_page2"
        tools:layout="@layout/fragment_main_page2">
        <action
            android:id="@+id/action_page1"
            app:destination="@id/page1Fragment" />
        <action
            android:id="@+id/action_page3"
            app:destination="@id/page3Fragment" />
    </fragment>
    <!-- TODO 同学们 这个是第三个 Fragment -->
    <!--    <navigation-->
    <!--        android:id="@+id/nav_graph_page3"-->
    <!--        app:startDestination="@id/page3Fragment">-->
    <fragment
        android:id="@+id/page3Fragment"
        android:name="com.example.myapplication.MainPage3Fragment"
        android:label="fragment_page3"
        tools:layout="@layout/fragment_main_page3">
        <action
            android:id="@+id/action_page2"
            app:destination="@id/page2Fragment" />
    </fragment>
</navigation>

通过startDestination设置它的初始fragment,

通过action去绑定跳到destination对应的fragment

因为是底部导航栏tab所以我们需要去设置menu

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
    android:id="@+id/page1Fragment"
    android:icon="@drawable/ic_launcher_foreground"
    android:title="第一页"/>
    <item
        android:id="@+id/page2Fragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="第二页"/>
​
    <item
        android:id="@+id/page3Fragment"
        android:icon="@drawable/ic_launcher_foreground"
        android:title="第三页"/>
</menu>

现在我们看MainActivity和它的布局

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >
    <!--
        app:defaultNavHost="true"
        拦截系统back键
    -->
<androidx.fragment.app.FragmentContainerView
    android:id="@+id/my_nav_host_fragment"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="9"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/nav_graph_main"
​
    />
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:itemTextColor="#ff0000"
        app:menu="@menu/menu"/>
​
</LinearLayout>
class MainActivity : AppCompatActivity() {
    var bootomNavigationView :BottomNavigationView?=null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bootomNavigationView=findViewById(R.id.nav_view)
​
        //让Navigation工作绑定
​
        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?
​
        val controller = navHostFragment!!.navController
​
        //底部导航栏tab+Navigation
        NavigationUI.setupWithNavController(bootomNavigationView!!,controller)
​
    }
    
}

现在我们就可以通过点击按钮和点击底部导航栏将他们进行切换

效果如下

分析源码


1.官网上: MainActivity去绑定Navigation(实际上:源码理解 Fragment帖到MainActivity)

我们先记住这个概念,后面详细了解中会进行解答

我们在详细分析源码先把它大致的流程用图画出来,并进行讲解

我们先看下图进行讲解

第一条线


1.首先MainActivity里有个NavHostFragment

NavHostFragment有个NavHost接口,而NavHostFragment就是为了临时存放一个Fragment

最终是把他黏贴在Activity之上

2.有个关键点它会调用NavController里的NavInflater

3.而NavInflater通过NavGraph找到它的Navigation.xml,进行解析Navigation.xml布局

4.最终解析出来之后变成NavGraph对象,此对象拥有所有的Graph导航信息集

以后操作此对象,就可以操作导航信息了

第二条线


1.它通过NavController控制器 拿到NavAction去调用navigate()方法

构造出NavDestInation,控制我们跳转到那个Fragment,

2.而NavDestInation只是导航信息指挥对象,它是领导者并不进行直接的操作,

它去指挥Navigator进行干活,为什么这么做呢

就是用来隔离,做了一个中转,为了更好扩展性,这是单一职责原则。

因为Navigator有很多子类的

你会发现jetpack很多系统源码搞这么多中间层

是因为任何高耦合低扩展的问题,都可以来一个中间层解决,方便以后的维护

MainActivity


现在我们来看MainActivity里的代码

 //让Navigation工作绑定

    val navHostFragment =
        supportFragmentManager.findFragmentById(R.id.my_nav_host_fragment) as NavHostFragment?

    val controller = navHostFragment!!.navController

    //底部导航栏tab+Navigation
    NavigationUI.setupWithNavController(bootomNavigationView!!,controller)

它是通过拿到NavHostFragment,再拿到控制器再将他们绑定,

那么按着我上面说的概念它是不是就是将NavHostFragmen帖到MainActivity上?

现在我们先跳进去看NavHostFragment的源码

可以看到它的构造函数是个空的,所以它最开始的create方法,注意这个create并不是生命周期的那个create

@NonNull
public static NavHostFragment create(@NavigationRes int graphResId) {
    return create(graphResId, null);
}

这个graphResId是什么,它其实就是我们那个导航的ID

我们先进去查看,它是调用了下面的create

@NonNull
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;
}

这里首先进行了Bundle的保存

然后再将这个 final NavHostFragment result = new NavHostFragment();

就是将自己实例化

onInflate


我们接下来要补充一个知识那就是fragment生命周期,我们都知道它是从onAttach()开始开始,但是有一种情况它在onAttach()前还有一个生命周期onInflate

1.onInflate(Context,AttributeSet,Bundle)只有硬编码在xml中的Fragment(即使用fragment标签)才会调用该方法,与自定义View十分类似,在实例化Xml布局时该方法会被调用

它的一些接收你可以查看下面文章

https://www.cnblogs.com/xiaowj/p/13913742.html

而我们其实也是在xml布局中NavHostFragment

@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);
    final int graphId = navHost.getResourceId(
            androidx.navigation.R.styleable.NavHost_navGraph, 0);
    if (graphId != 0) {
        mGraphId = graphId;
    }
    navHost.recycle();

    final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
    final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
    if (defaultHost) {
        mDefaultNavHost = true;
    }
    a.recycle();
}

看到这个TypedArray有过自定义View的人就会很熟悉就是在解析xml布局

而我们的XML布局其中如下

<androidx.fragment.app.FragmentContainerView
android:id="@+id/my_nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="9"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
/>

先解析XMl布局

就开始走第二步

final int graphId = navHost.getResourceId( androidx.navigation.R.styleable.NavHost_navGraph, 0);

XMl布局拿到我们的navGraph,拿到了它的ID int graphId

在它的下面解析到了app:defaultNavHost="true"

这里就不在过多的介绍,

onCreateNavController


那么现在我们发现其中有个onCreateNavController,我们在MainActivity中有用到navController

而且在上面讲的流程中不管是第一条线还是第二条线都离不开他

我们来看他的代码

@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
// 这里是通过导航暴露者,获取到 NavigationProvider 对象
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}

其实 这里是通过导航暴露者,获取到 NavigationProvider 对象

NavigationProvider


我们查看NavigationProvider

@NonNull
public NavigatorProvider getNavigatorProvider() {
    return mNavigatorProvider;
}
@SuppressLint("TypeParameterUnusedInFormals")
public class NavigatorProvider {
private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private static boolean validateName(String name) {
return name != null && !name.isEmpty();
}
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
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());
}
sAnnotationNames.put(navigatorClass, name);
}
return name;
}

看到了吗它最顶部HashMap,我们知道在很多源码中map就是用来做缓存的作用,

所以看到这其实我们就可以知道这个HashMap就是为了把我们的三个fragment进行保存

那我们就明白了原来onCreateNavController就是为了初始化它的容器,再把它的暴露者初始化就行

但是其实会有疑惑为什么onCreateNavController写了两个getNavigatorProvider(),

因为我们出入在xml写入的方式,还可以通过代码写入,第二种createFragmentNavigator就是

如果你通过代码写入的方式,去存储fragment

onCreate


接下来我们再查看它的生命周期函数onCreate

public void onCreate(@Nullable Bundle savedInstanceState) {
    final Context context = requireContext();

    mNavController = new NavHostController(context);
    mNavController.setLifecycleOwner(this);
    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());
    onCreateNavController(mNavController);

    Bundle navState = null;
    if (savedInstanceState != null) {
        navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
        if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
            mDefaultNavHost = true;
            getParentFragmentManager().beginTransaction()
                    .setPrimaryNavigationFragment(this)
                    .commit();
        }
        mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
    }

    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);
        }
    }

    // We purposefully run this last as this will trigger the onCreate() of
    // child fragments, which may be relying on having the NavController already
    // created and having its state restored by that point.
    super.onCreate(savedInstanceState);
}

它第一步就是实例化控制器NavHostController,设置lifecycler监听生命周期,

设置ViewModel保证数据在横竖屏切换不丢失,以及拦截back返回键

再就是我们上面提到的 onCreateNavController(mNavController);

而if判断就是为了拿到Bundle里面的值以及进行FragmentManager的操作

现在我们看重点

if (mGraphId != 0) {
// Set from onInflate()
mNavController.setGraph(mGraphId);
}

就是拿到我们解析出来的navigation.xml导航图

将我们的布局设置进去

我们再进去查看,一层层的往下调用

@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
    setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}

这个startDestinationArgs就是在navigation.xml设置的我们第一个启动的fragment

我们去查看它的inflate

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();
        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();
    }
}

XmlResourceParser就是安卓原生的解析方式,去解析布局

从开头解析到结尾最终返回我们的NavGraph,就是我们在上面提到的.最终解析出来之后变成NavGraph对象,

我们继续看下一层的

@CallSuper
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);
}

这个if判断,是进行弹栈,如果我们是首次打开首页不会进行这个操作,

当我们进行fragment的切换,按下返回键时候就会进行弹栈

而下面的nGraphCreated(startDestinationArgs);设置默认启动的frgment

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 (mBackStackToRestore != null) {
        for (Parcelable parcelable : mBackStackToRestore) {
            NavBackStackEntryState state = (NavBackStackEntryState) parcelable;
            NavDestination node = findDestination(state.getDestinationId());
            if (node == null) {
                final String dest = NavDestination.getDisplayName(mContext,
                        state.getDestinationId());
                throw new IllegalStateException("Restoring the Navigation back stack failed:"
                        + " destination " + dest
                        + " cannot be found from the current destination "
                        + getCurrentDestination());
            }
            Bundle args = state.getArgs();
            if (args != null) {
                args.setClassLoader(mContext.getClassLoader());
            }
            NavBackStackEntry entry = new NavBackStackEntry(mContext, node, args,
                    mLifecycleOwner, mViewModel,
                    state.getUUID(), state.getSavedState());
            mBackStack.add(entry);
        }
        updateOnBackPressedCallbackEnabled();
        mBackStackToRestore = null;
    }
    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();
    }
}

这里很复杂,我也没研究透,但是可以大概知道如果我们在xml布局里设置了startDestination设置初始化启动的fragment,以及对栈的管理

我们在看

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);
}
}

主要是执行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());
        }
    }
    Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
            node.getNavigatorName());
    Bundle finalArgs = node.addInDefaultArgs(args);
    NavDestination newDest = navigator.navigate(node, finalArgs,
            navOptions, navigatorExtras);
    if (newDest != null) {
        if (!(newDest instanceof FloatingWindow)) {
            // We've successfully navigating to the new destination, which means
            // we should pop any FloatingWindow destination off the back stack
            // before updating the back stack with our new destination
            //noinspection StatementWithEmptyBody
            while (!mBackStack.isEmpty()
                    && mBackStack.peekLast().getDestination() instanceof FloatingWindow
                    && popBackStackInternal(
                            mBackStack.peekLast().getDestination().getId(), true)) {
                // Keep popping
            }
        }

        // When you navigate() to a NavGraph, we need to ensure that a new instance
        // is always created vs reusing an existing copy of that destination
        ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
        NavDestination destination = newDest;
        if (node instanceof NavGraph) {
            do {
                NavGraph parent = destination.getParent();
                if (parent != null) {
                    NavBackStackEntry entry = new NavBackStackEntry(mContext, parent,
                            finalArgs, mLifecycleOwner, mViewModel);
                    hierarchy.addFirst(entry);
                    // Pop any orphaned copy of that navigation graph off the back stack
                    if (!mBackStack.isEmpty()
                            && mBackStack.getLast().getDestination() == parent) {
                        popBackStackInternal(parent.getId(), true);
                    }
                }
                destination = parent;
            } while (destination != null && destination != node);
        }

        // Now collect the set of all intermediate NavGraphs that need to be put onto
        // the back stack
        destination = hierarchy.isEmpty()
                ? newDest
                : hierarchy.getFirst().getDestination();
        while (destination != null && findDestination(destination.getId()) == null) {
            NavGraph parent = destination.getParent();
            if (parent != null) {
                NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
                        mLifecycleOwner, mViewModel);
                hierarchy.addFirst(entry);
            }
            destination = parent;
        }
        NavDestination overlappingDestination = hierarchy.isEmpty()
                ? newDest
                : hierarchy.getLast().getDestination();
        // Pop any orphaned navigation graphs that don't connect to the new destinations
        //noinspection StatementWithEmptyBody
        while (!mBackStack.isEmpty()
                && mBackStack.getLast().getDestination() instanceof NavGraph
                && ((NavGraph) mBackStack.getLast().getDestination()).findNode(
                        overlappingDestination.getId(), false) == null
                && popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) {
            // Keep popping
        }
        mBackStack.addAll(hierarchy);
        // The mGraph should always be on the back stack after you navigate()
        if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) {
            NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
                    mLifecycleOwner, mViewModel);
            mBackStack.addFirst(entry);
        }
        // And finally, add the new destination with its default args
        NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
                newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
        mBackStack.add(newBackStackEntry);
    } else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
        launchSingleTop = true;
        NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
        if (singleTopBackStackEntry != null) {
            singleTopBackStackEntry.replaceArguments(finalArgs);
        }
    }
    updateOnBackPressedCallbackEnabled();
    if (popped || newDest != null || launchSingleTop) {
        dispatchOnDestinationChanged();
    }
}

我们重点看这段

Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);

就是为了拿到我们设置的startDestination,找到我们的第一个fragment,也是我们在fragment用到的

我们查看我们之前创建的Fragment

class MainPage1Fragment :Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_main_page1,container,false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //点击事件跳转到第二个fragment
        val btn = view.findViewById<Button>(R.id.btn)
        btn.setOnClickListener { view ->
            Navigation.findNavController(view).navigate(R.id.action_page2)
        }
    }
}

我们可以看到要跳转显示的fragment也是调用了navigate

在进去查看navigate,就是我们用到的同一个方法

这就是我上面说的

NavDestInation只是导航信息指挥对象,它是领导者并不进行直接的操作,

它去指挥Navigator进行干活

Navigator


我们去查看Navigator‘

public abstract class Navigator<D extends NavDestination> {
    /**
     * This annotation should be added to each Navigator subclass to denote the default name used
     * to register the Navigator with a {@link NavigatorProvider}.
     *
     * @see NavigatorProvider#addNavigator(Navigator)
     * @see NavigatorProvider#getNavigator(Class)
     */
    @Retention(RUNTIME)
    @Target({TYPE})
    @SuppressWarnings("UnknownNullness") // TODO https://issuetracker.google.com/issues/112185120
    public @interface Name {
        String value();
    }

    /**
     * Construct a new NavDestination associated with this Navigator.
     *
     * <p>Any initialization of the destination should be done in the destination's constructor as
     * it is not guaranteed that every destination will be created through this method.</p>
     * @return a new NavDestination
     */
    @NonNull
    public abstract D createDestination();

    /**
     * Navigate to a destination.
     *
     * <p>Requests navigation to a given destination associated with this navigator in
     * the navigation graph. This method generally should not be called directly;
     * {@link NavController} will delegate to it when appropriate.</p>
     *
     * @param destination destination node to navigate to
     * @param args arguments to use for navigation
     * @param navOptions additional options for navigation
     * @param navigatorExtras extras unique to your Navigator.
     * @return The NavDestination that should be added to the back stack or null if
     * no change was made to the back stack (i.e., in cases of single top operations
     * where the destination is already on top of the back stack).
     */
    @Nullable
    public abstract NavDestination navigate(@NonNull D destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras);

    /**
     * Attempt to pop this navigator's back stack, performing the appropriate navigation.
     *
     * <p>Implementations should return {@code true} if navigation
     * was successful. Implementations should return {@code false} if navigation could not
     * be performed, for example if the navigator's back stack was empty.</p>
     *
     * @return {@code true} if pop was successful
     */
    public abstract boolean popBackStack();

    /**
     * Called to ask for a {@link Bundle} representing the Navigator's state. This will be
     * restored in {@link #onRestoreState(Bundle)}.
     */
    @Nullable
    public Bundle onSaveState() {
        return null;
    }

    /**
     * Restore any state previously saved in {@link #onSaveState()}. This will be called before
     * any calls to {@link #navigate(NavDestination, Bundle, NavOptions, Navigator.Extras)} or
     * {@link #popBackStack()}.
     * <p>
     * Calls to {@link #createDestination()} should not be dependent on any state restored here as
     * {@link #createDestination()} can be called before the state is restored.
     *
     * @param savedState The state previously saved
     */
    public void onRestoreState(@NonNull Bundle savedState) {
    }

    /**
     * Interface indicating that this class should be passed to its respective
     * {@link Navigator} to enable Navigator specific behavior.
     */
    public interface Extras {
    }
}

它就是个抽象类,我们可以看它有6个子类继承了它,我们比较常用的Fragment

FragmentNavigator

所以我们去看FragmentNavigator

@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;
    }
    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);

    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);
    ft.commit();
    // The commit succeeded, update our view of the world
    if (isAdded) {
        mBackStack.add(destId);
        return destination;
    } else {
        return null;
    }
}

这里就是把我们千辛万苦保存的startDestination通过反射去找到这个fragment

再将他交给FragmentManager进行管理

再下面就是它fragment的进出动画

重点是它如何进行替换的,

ft.replace(mContainerId, frag);

这里的mContainerId就是我们xml布局里的NavHostFragment

就是将我们在mainactivity里的临时NavHostFragment与我们要显示的fragment进行替换

<androidx.fragment.app.FragmentContainerView
android:id="@+id/my_nav_host_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="9"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph_main"
/>

为什么这么说呢,因为我们在NavHostFragment里面有个createFragmentNavigator()方法

@Deprecated
@NonNull
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
    return new FragmentNavigator(requireContext(), getChildFragmentManager(),
            getContainerId());
}
private int getContainerId() {
    int id = getId();
    if (id != 0 && id != View.NO_ID) {
        return id;
    }
    // Fallback to using our own ID if this Fragment wasn't added via
    // add(containerViewId, Fragment)
    return R.id.nav_host_fragment_container;
}

getContainerId()就是把我们的NavHostFragment的id返回

10 小结

小结:

第一步:解析所有的目的地

第二步:放到一个HashMap里面全部保存好

第三步:获取第一个目的地Fragment

第四步:初始化目的地的所有信息

第五步:显示第一个目的地Fragment 画面

额外补充

额外补充


NavHostFragment.onViwCreated
这里有一个 Fragment的生命周期函数,忘记说了
@Override
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");
    }
    Navigation.setViewNavController(view, mNavController); // 会把我们的 mNavController 加入到 Navigation里面去
​
    // 说白了,如果是代码的方式去写的话,并非是xml的写法的话,就做下面的代码,但是 大部分都是 xml的形式
    // 当以编程方式添加时,我们需要在父级上设置 NavController - 即具有与此 NavHostFragment 匹配的 ID 的视图
    // When added programmatically, we need to set the NavController on the parent - i.e.,
    // the View that has the ID matching this NavHostFragment.
    if (view.getParent() != null) {
        mViewParent = (View) view.getParent();
        if (mViewParent.getId() == getId()) {
            Navigation.setViewNavController(mViewParent, mNavController);
        }
    }
}

>>> 下面是使用navigation角度上分析源码
1 MainPage1Fragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
​
    val btn = view.findViewById<Button>(R.id.btn)
    btn.setOnClickListener { view ->
        Navigation.findNavController(view).navigate(R.id.action_page2) // 在这里开始导航
    }
}
2 NavController.navigation
public void navigate(@IdRes int resId) {
    navigate(resId, null);
}
​
public void navigate(@IdRes int resId, @Nullable Bundle args) {
    navigate(resId, args, null);
}
​
public void navigate(@IdRes int resId, @Nullable Bundle args,
        @Nullable NavOptions navOptions) {
    navigate(resId, args, navOptions, null);
}
​
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
        @Nullable Navigator.Extras navigatorExtras) {
    // 目的 是在 Graph里面去寻找 当前节点的Fragment 为 目的地
    NavDestination currentNode = mBackStack.isEmpty() // 先去看看栈有没有
            ? mGraph
            : mBackStack.getLast().getDestination(); // 如果栈没有,就取最后一个
    if (currentNode == null) {
        throw new IllegalStateException("no current navigation node");
    }
    @IdRes int destId = resId;
    // 获取节点后,就要去获取 action 导航动作,只有action才能通过resId 获取下一个要导航的目标id
    final NavAction navAction = currentNode.getAction(resId); 
    Bundle combinedArgs = null;
    if (navAction != null) {
        if (navOptions == null) {
            navOptions = navAction.getNavOptions();
        }
        destId = navAction.getDestinationId(); // 获取下一个目的地的 id号信息等
        Bundle navActionArgs = navAction.getDefaultArguments();
        if (navActionArgs != null) {
            combinedArgs = new Bundle();
            combinedArgs.putAll(navActionArgs);
        }
    }
    
    if (args != null) {
        if (combinedArgs == null) {
            combinedArgs = new Bundle();
        }
        combinedArgs.putAll(args);
    }
​
    if (destId == 0 && navOptions != null && navOptions.getPopUpTo() != -1) {
        popBackStack(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive());
        return;
    }
​
    if (destId == 0) {
        throw new IllegalArgumentException("Destination id == 0 can only be used"
                + " in conjunction with a valid navOptions.popUpTo");
    }
     
    // 下面代码只是一个检查健壮性而已,先不管
    NavDestination node = findDestination(destId);
    if (node == null) {
        final String dest = NavDestination.getDisplayName(mContext, destId);
        if (navAction != null) {
            throw new IllegalArgumentException("Navigation destination " + dest
                    + " referenced from action "
                    + NavDestination.getDisplayName(mContext, resId)
                    + " cannot be found from the current destination " + currentNode);
        } else {
            throw new IllegalArgumentException("Navigation action/destination " + dest
                    + " cannot be found from the current destination " + currentNode);
        }
    }
    // 这行代码就是我们要关心的主线流程
    navigate(node, combinedArgs, navOptions, navigatorExtras);
}
3 NavController.navigation
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());
        }
    }
    // 同学们,这里又回到了,最初开始的代码哦
    Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
            node.getNavigatorName());
    Bundle finalArgs = node.addInDefaultArgs(args);
    // 同学们 还记得 新的目的地么? OK 分析完毕了,无效重复分析了
    NavDestination newDest = navigator.navigate(node, finalArgs,
            navOptions, navigatorExtras);

    if (newDest != null) {
        if (!(newDest instanceof FloatingWindow)) {
            // We've successfully navigating to the new destination, which means
            // we should pop any FloatingWindow destination off the back stack
            // before updating the back stack with our new destination
            //noinspection StatementWithEmptyBody
            while (!mBackStack.isEmpty()
                    && mBackStack.peekLast().getDestination() instanceof FloatingWindow
                    && popBackStackInternal(
                            mBackStack.peekLast().getDestination().getId(), true)) {
                // Keep popping
            }
        }

        // When you navigate() to a NavGraph, we need to ensure that a new instance
        // is always created vs reusing an existing copy of that destination
        ArrayDeque<NavBackStackEntry> hierarchy = new ArrayDeque<>();
        NavDestination destination = newDest;
        if (node instanceof NavGraph) {
            do {
                NavGraph parent = destination.getParent();
                if (parent != null) {
                    NavBackStackEntry entry = new NavBackStackEntry(mContext, parent,
                            finalArgs, mLifecycleOwner, mViewModel);
                    hierarchy.addFirst(entry);
                    // Pop any orphaned copy of that navigation graph off the back stack
                    if (!mBackStack.isEmpty()
                            && mBackStack.getLast().getDestination() == parent) {
                        popBackStackInternal(parent.getId(), true);
                    }
                }
                destination = parent;
            } while (destination != null && destination != node);
        }

        // Now collect the set of all intermediate NavGraphs that need to be put onto
        // the back stack
        destination = hierarchy.isEmpty()
                ? newDest
                : hierarchy.getFirst().getDestination();
        while (destination != null && findDestination(destination.getId()) == null) {
            NavGraph parent = destination.getParent();
            if (parent != null) {
                NavBackStackEntry entry = new NavBackStackEntry(mContext, parent, finalArgs,
                        mLifecycleOwner, mViewModel);
                hierarchy.addFirst(entry);
            }
            destination = parent;
        }
        NavDestination overlappingDestination = hierarchy.isEmpty()
                ? newDest
                : hierarchy.getLast().getDestination();
        // Pop any orphaned navigation graphs that don't connect to the new destinations
        //noinspection StatementWithEmptyBody
        while (!mBackStack.isEmpty()
                && mBackStack.getLast().getDestination() instanceof NavGraph
                && ((NavGraph) mBackStack.getLast().getDestination()).findNode(
                        overlappingDestination.getId(), false) == null
                && popBackStackInternal(mBackStack.getLast().getDestination().getId(), true)) {
            // Keep popping
        }
        mBackStack.addAll(hierarchy);
        // The mGraph should always be on the back stack after you navigate()
        if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) {
            NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
                    mLifecycleOwner, mViewModel);
            mBackStack.addFirst(entry); // 留意一下,每次都会管理 进栈
        }
        // And finally, add the new destination with its default args
        NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
                newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
        mBackStack.add(newBackStackEntry);
    } else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
        launchSingleTop = true;
        NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
        if (singleTopBackStackEntry != null) {
            singleTopBackStackEntry.replaceArguments(finalArgs);
        }
    }
    updateOnBackPressedCallbackEnabled();
    if (popped || newDest != null || launchSingleTop) {
        dispatchOnDestinationChanged();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值