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 的跳转一起来学习

相关推荐
Lei活在当下13 小时前
【业务场景架构实战】4. 支付状态分层流转的设计和实现
架构·android jetpack·响应式设计
天花板之恋1 天前
Compose之图片加载显示
android jetpack
消失的旧时光-19432 天前
Kotlinx.serialization 使用讲解
android·数据结构·android jetpack
Tans52 天前
Androidx Fragment 源码阅读笔记(下)
android jetpack·源码阅读
Lei活在当下3 天前
【业务场景架构实战】2. 对聚合支付 SDK 的封装
架构·android jetpack
Tans55 天前
Androidx Fragment 源码阅读笔记(上)
android jetpack·源码阅读
alexhilton6 天前
runBlocking实践:哪里该使用,哪里不该用
android·kotlin·android jetpack
Tans59 天前
Androidx Lifecycle 源码阅读笔记
android·android jetpack·源码阅读
ljt272496066110 天前
Compose笔记(四十九)--SwipeToDismiss
android·笔记·android jetpack
4z3312 天前
Jetpack Compose重组优化:机制剖析与性能提升策略
性能优化·android jetpack