Navigation 源码解析(一)

本篇文章比较适合对 Navigation 有一定了解的同学,因为由于篇幅的原因,本篇文章只会对 Navigation 的源码以及原来做讲解,下面我们进入正文

在 NavHostFragment 的初始化工作中,具体的工作我们可以根据他的生命周期方法来跟踪,但是在使用的他的时候有2种方式

1: 代码手动创建,使用这种方式 那么就需要使用 NavHostFragment 的crate 方法 来创建,将 GraphId 与 startDestinationArgs 传入 ,不过这种情况比较少见

less 复制代码
@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;
}

2: 在 xml 中引入

ini 复制代码
<fragment
    android:id="@+id/tsm_first_navigation_host"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/tsm_navigation"
    android:name="androidx.navigation.fragment.NavHostFragment"/>

需要注意的是 这里指定的是name 就是我们的目标 Fragment

在xml 中引入就会调用 Fragment 的 onInflate 方法,我们来看看 onInflate 方法都做了哪些工作

less 复制代码
@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);
    //   找到我们的 navigation xml 文件
    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) {
    // 生命自己是主导航栏,绑定 NavigationMenu 使用
        mDefaultNavHost = true;
    }
    a.recycle();
}

执行完 onInflate 就进入到了 NavHostFragment 的生命周期方法,第一个进入的方法就是 onAttach 方法

less 复制代码
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    // TODO This feature should probably be a first-class feature of the Fragment system,
    // but it can stay here until we can add the necessary attr resources to
    // the fragment lib.
    if (mDefaultNavHost) {
        getParentFragmentManager().beginTransaction()
                // 升级Fragment 的权限,接管系统返回栈
                .setPrimaryNavigationFragment(this)
                .commit();
    }
}

别看这个方法不起眼,但是这里的 setPrimaryNavigationFragment 这个方法是一个至关重要的方法,这个方法让当前的这个Fragment 升级,并且接管系统返回键的操作,具体的内容我们聊到 Navigation 的返回栈的时候会说

离开了这里就来到了 onCreate 方法,

scss 复制代码
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    final Context context = requireContext();
    // 重要方法1 
    mNavController = new NavHostController(context);
    mNavController.setLifecycleOwner(this);
    // 重要方法2
    mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
    // Set the default state - this will be updated whenever
    // onPrimaryNavigationFragmentChanged() is called
    mNavController.enableOnBackPressed(
            mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
    mIsPrimaryBeforeOnCreate = null;
    mNavController.setViewModelStore(getViewModelStore());
    // 重要方法3 
    onCreateNavController(mNavController);

    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()
        // 重要方法4
        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 ,

ini 复制代码
public NavController(@NonNull Context context) {
    mContext = context;
    while (context instanceof ContextWrapper) {
        if (context instanceof Activity) {
            mActivity = (Activity) context;
            break;
        }
        context = ((ContextWrapper) context).getBaseContext();
    }
    mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
    mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}

在创建 NavHostController 的创建过程中 向他私有 变量 mNavigatorProvider 中添加了2个 Navigator NavGraphNavigation 与 ActivityNavigator , 具体保存方式下

less 复制代码
@Navigator.Name("navigation")
public class NavGraphNavigator extends Navigator<NavGraph> {
}

@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination> {
}

public class NavigatorProvider {
    // 保存不同种类的 Navigator 的名字
    private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();
    /// 根据名字保存不同种类的 Navigator 的实例
    private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
        new HashMap<>();
    
    // 通过Class 上的 @Navigator.Name 注释来获取这个类型的名字
    @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;
    }
    
    // 调用保存方法,不同类型的实例只能有一个,所以使用 name 来作为key ,name 是根据类注解来获取的
    @Nullable
    public final Navigator<? extends NavDestination> addNavigator(
            @NonNull Navigator<? extends NavDestination> navigator) {
        String name = getNameForNavigator(navigator.getClass());
        return addNavigator(name, navigator);
    }
    
    //使用HashMap<String ,Navigation> 来保存数据
    @CallSuper
    @Nullable
    public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
            @NonNull Navigator<? extends NavDestination> navigator) {
        if (!validateName(name)) {
            throw new IllegalArgumentException("navigator name cannot be an empty string");
        }
        return mNavigators.put(name, navigator);
    }
}

从上面代码可以看到 NavigatorProvider 添加Navigator 先通过 添加的Navigator 的实例获取Class,在通过注解获取 name,同时将获取的name 与 Class 也使用HashMap 缓存起来(框架都喜欢这么干,空间换时间) ,最后来保存的.

重要方法2 也是返回栈相关的问题,这个需要先将前面的基础搞懂再去分析

重要方法3: onCreateNavController(mNavController); 先来看一下方法的具体实现再做分析

less 复制代码
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
    navController.getNavigatorProvider().addNavigator(
            new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
    navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}

这方法看着是不是和我们上面讲到的重要方法1里面的内容极其的相似,简直就是一个内容,到了这里NavigatorProvider 中的 4类 Navigator 我们已经知道了

json 复制代码
{
    "navigation"  :  NavGraphNavigator
    "activity"    : ActivityNavigator
    "dialog"      : DialogFragmentNavigator
    "fragment"    : FragmentNavigator
}

接下来就到了重要方法4:mNavController.setGraph 这段代码最终会调用到 下面2段代码,

less 复制代码
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
    // 这里引入了一个新的 inflate ,NavInflater 
    setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}

@CallSuper
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
    // 设置新的Graph ,如果之前的Graph 存在,就需要走回退栈
    if (mGraph != null) {
        // Pop everything from the old graph off the back stack
        popBackStackInternal(mGraph.getId(), true);
    }
    // 将新的Graph 赋值给当前的Graph ,并且开始寻找 Destination 目标
    mGraph = graph;
    onGraphCreated(startDestinationArgs);
}

这里新引入了一个新的 inflate , 这个inflate 的内容相对于View 来说还简单一点 ,具体的解析过程大致内容如下

less 复制代码
@NonNull
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
        @NonNull AttributeSet attrs, int graphResId)
        throws XmlPullParserException, IOException {
    //注释1. 解析跟节点 , 如果是解析xml ,那么根据xml 第一个节点的名字 navigation
    //得到的就是 NavGraphNavigator
    // 在逐层遍历的过程中,这个可以是fragment  include中的 navigation  activity 等,
    Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
    //注释2.xml跟节点进来就是创建一个导航节点 NavGraph
    // 如果provider 是 不是navigation ,创建的就是其他的 比如 activity 创建的是Destination
    final NavDestination dest = navigator.createDestination();
    // 在这里 NavGraph 设置的 setStartDestination 开始节点,父类设置的 name id  label
    dest.onInflate(mContext, attrs);

    final int innerDepth = parser.getDepth() + 1;
    int type;
    int depth;
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && ((depth = parser.getDepth()) >= innerDepth
            || type != XmlPullParser.END_TAG)) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        if (depth > innerDepth) {
            continue;
        }

        final String name = parser.getName();
        if (TAG_ARGUMENT.equals(name)) {
            // 设置参数
            inflateArgumentForDestination(res, dest, attrs, graphResId);
        } else if (TAG_DEEP_LINK.equals(name)) {
            //设置deepLink
            inflateDeepLink(res, dest, attrs);
        } else if (TAG_ACTION.equals(name)) {
            // 设置action
            inflateAction(res, dest, attrs, parser, graphResId);
        } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
            // 设置 include ,也就是重新创建一个子节点
            final TypedArray a = res.obtainAttributes(
                    attrs, androidx.navigation.R.styleable.NavInclude);
            final int id = a.getResourceId(
                    androidx.navigation.R.styleable.NavInclude_graph, 0);
            ((NavGraph) dest).addDestination(inflate(id));
            a.recycle();
        } else if (dest instanceof NavGraph) {
            //注释3. 如果是节点NavGraph,就根据 当前节点继续添加, 在 1 这个地方我们得到的就是 fragment 
            //以此类推
            ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
        }
    }

    return dest;
}

看了上面的注解相信你一定能搞清楚整个解析过程,但是这里还有一个比较重要的内容,那就是 NavGraph 中的子节点是使用 SparseArrayCompat 来保存的,同时 mStartDestIdName 与子节点列表是平级的关系

arduino 复制代码
final SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
private String mStartDestIdName;

我们就以一个

xml 复制代码
<navigation>
    <fragment></fragment>
    <fragment></fragment>
    <fragment></fragment>
</navigation>

看完了这里我们再来分析一下 NavigatorProvider 中节点的关系

先根据 navigation 去provider 中找到 NavGraphNavigator ,然后通过 NavGraphNavigator 创建出来一个 NavGraph, 这个NavGraph 中包含一个 mStartDestIdName 和一个 SparseArrayCompat , 在while 中会走3次 注释3,并向 NavGraph 的 mNode 中添加3个子节点,具体的添加方法如下

java 复制代码
public final void addDestination(@NonNull NavDestination node) {
    int id = node.getId();
    if (id == 0) {
        throw new IllegalArgumentException("Destinations must have an id."
                + " Call setId() or include an android:id in your navigation XML.");
    }
    if (id == getId()) {
        throw new IllegalArgumentException("Destination " + node + " cannot have the same id "
                + "as graph " + this);
    }
    NavDestination existingDestination = mNodes.get(id);
    if (existingDestination == node) {
        return;
    }
    if (node.getParent() != null) {
        throw new IllegalStateException("Destination already has a parent set."
                + " Call NavGraph.remove() to remove the previous parent.");
    }
    if (existingDestination != null) {
        existingDestination.setParent(null);
    }
    node.setParent(this);
    mNodes.put(node.getId(), node);
}

在添加过程中先将自己点的 parent 设置为自己,然后在添加到 SparseArrayCompat 当中,整个添加过程就结束了

整个初始化的工作就已经分析的差不多了,剩下的就是设置 startDestion 这个工作了,由于时间和内容的关系,可以将剩下的部分同 navigation 的跳转一起来学习

相关推荐
ljt272496066114 小时前
Compose笔记(二十三)--多点触控
笔记·android jetpack
我命由我1234517 天前
Android 解绑服务问题:java.lang.IllegalArgumentException: Service not registered
android·java·开发语言·java-ee·安卓·android jetpack·android-studio
我命由我1234519 天前
MQTT - Android MQTT 编码实战(MQTT 客户端创建、MQTT 客户端事件、MQTT 客户端连接配置、MQTT 客户端主题)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
前行的小黑炭19 天前
Android LiveData源码分析:为什么他刷新数据比Handler好,能更节省资源,解决内存泄漏的隐患;
android·kotlin·android jetpack
_一条咸鱼_19 天前
深度剖析:Java PriorityQueue 使用原理大揭秘
android·面试·android jetpack
_一条咸鱼_19 天前
揭秘 Java PriorityBlockingQueue:从源码洞悉其使用原理
android·面试·android jetpack
_一条咸鱼_19 天前
深度揭秘:Java LinkedList 源码级使用原理剖析
android·面试·android jetpack
_一条咸鱼_19 天前
深入剖析 Java LinkedBlockingQueue:源码级别的全面解读
android·面试·android jetpack
_一条咸鱼_19 天前
探秘 Java DelayQueue:源码级剖析其使用原理
android·面试·android jetpack
_一条咸鱼_19 天前
揭秘 Java ArrayDeque:从源码到原理的深度剖析
android·面试·android jetpack