美文网首页
Navigation的源码解析

Navigation的源码解析

作者: 烧伤的火柴 | 来源:发表于2020-05-10 11:36 被阅读0次

介绍

Google宣布推出Navigation组件,作为构建您的应用内界面的框架,重点是让单 Activity 应用成为首选架构。利用Navigation组件对 Fragment 的原生支持,您可以获得架构组件的所有好处(例如生命周期和 ViewModel),同时让此组件为您处理 FragmentTransaction 的复杂性。此外,Navigation组件还可以让您声明我们为您处理的转场。它可以自动构建正确的“向上”和“返回”行为,包含对深层链接的完整支持,并提供了帮助程序,用于将导航关联到合适的 UI 小部件,例如抽屉式导航栏和底部导航。

关于Navigation的使用,这里不在介绍,可以看google的例子Codelabs-Android-Navigation
,所有使用方法基本都涵盖了。
本篇文章主要介绍Navigation 的思想。

NavHostFragment

我们在使用的时候,是在xml中定义NavHostFragment,添加自定义属性。

<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"
    tools:context=".MainActivity">
    ...
    <fragment
        android:id="@+id/myNavHostFragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        app:defaultNavHost="true"
        app:navGraph="@navigation/mobile_navigation" />
    ...
</LinearLayout>

按照Fragment的生命周期进行分析

onInflate

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

方法内部主要是解析我们传递的navGraph和defaultNavHost属性然后赋值给mGraphId和mDefaultNavHost 。

onAttach方法

   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()
                    .setPrimaryNavigationFragment(this)
                    .commit();
        }
    }

主要是这是this作为主导航的容器

onCreate方法

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

        mNavController = new NavHostController(context);//1
        mNavController.setLifecycleOwner(this);
        mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
        // Set the default state - this will be updated whenever
        // onPrimaryNavigationFragmentChanged() is called
        mNavController.enableOnBackPressed(//2
                mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
        mIsPrimaryBeforeOnCreate = null;
        mNavController.setViewModelStore(getViewModelStore());
        onCreateNavController(mNavController);//3
        //4
        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) {//5
            // 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的地方创建NavHostController的实例mNavController
我们看一下构造方法内是怎么实现的?

 public NavHostController(@NonNull Context context) {
        super(context);
    }

父类的构造是

    public NavController(@NonNull Context context) {
        ....
        mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
        mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
    }

主要的逻辑是把NavGraphNavigator和ActivityNavigator的实例放到了mNavigatorProvider的存储器中。
NavigatorProvider内主要通过一个hashmap保存key是xxxNavigator的注解,value是xxxNavigator的实例。

我们回到onCreate的注释2处是使用mNavController处理系统的返回事件。
注释3处的方法是

 protected void onCreateNavController(@NonNull NavController navController) {
        navController.getNavigatorProvider().addNavigator(
                new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
        navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
    }

向mNavController的mNavigatorProvider容器添加两个Fragment实例 DialogFragmentNavigator和FragmentNavigator
通过构造和这里逻辑后,mNavigatorProvider容器内有四条记录

("navigation",NavGraphNavigator)
("activity",ActivityNavigator)
("dialog",DialogFragmentNavigator)
("fragment","FragmentNavigator")

以上所有部分不想看文字的可以看以下图片


创建解析器.png

注释4的地方是防止Fragment还原的时候出现重叠的现象。
最重要的是注释5的地方

 public void setGraph(@NavigationRes int graphResId) {
        setGraph(graphResId, null);
    }

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

 public NavInflater getNavInflater() {
        if (mInflater == null) {
            mInflater = new NavInflater(mContext, mNavigatorProvider);
        }
        return mInflater;
    }

在创建NavInflater实例的时候,把mNavigatorProvider传递进去了。我们看一下NavInflater的inflate方法

 public NavGraph inflate(@NavigationRes int graphResId) {
        Resources res = mContext.getResources();
        XmlResourceParser parser = res.getXml(graphResId);
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        try {
           ...
            NavDestination destination = inflate(res, parser, attrs, graphResId);
            ...
            return (NavGraph) destination;
        } catch (Exception e) {
            throw new RuntimeException("Exception inflating "
                    + res.getResourceName(graphResId) + " line "
                    + parser.getLineNumber(), e);
        } finally {
            parser.close();
        }
    }

private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
            @NonNull AttributeSet attrs, int graphResId)
            throws XmlPullParserException, IOException {
        Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());//1
        final NavDestination dest = navigator.createDestination();//2

        dest.onInflate(mContext, attrs);//3

        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)) {//4
             ....
            final String name = parser.getName();
            if (TAG_ARGUMENT.equals(name)) {
                inflateArgumentForDestination(res, dest, attrs, graphResId);
            } else if (TAG_DEEP_LINK.equals(name)) {
                inflateDeepLink(res, dest, attrs);
            } else if (TAG_ACTION.equals(name)) {
                inflateAction(res, dest, attrs, parser, graphResId);
            } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
                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) {
                ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
            }
        }

        return dest;
    }

这个解析过程要结合我们自己写的导航配置文件,我的是mobile_navigation.xml,根节点是navigation,注释1处得到的是NavGraphNavigator实例,注释2处是NavGraph实例

public NavGraph createDestination() {
        return new NavGraph(this);
    }

NavGraph的属性

public class NavGraph extends NavDestination implements Iterable<NavDestination> {
    @SuppressWarnings("WeakerAccess") /* synthetic access */
    final SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
    private int mStartDestId;
    private String mStartDestIdName;
    ···
}

NavGraph 和子节点组合成了“部分-整体”的层次结构,也就是组合模式。navigation节点下的左右子节点都是保存在NavGraph的mNodes中。mStartDestId就是我们的定义的app:startDestination="@id/home_dest"

回到inflate方法的注释3处,dest这个NavGraph。NavGraph#onInflate

  @Override
    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
        super.onInflate(context, attrs);
        TypedArray a = context.getResources().obtainAttributes(attrs,
                R.styleable.NavGraphNavigator);
        setStartDestination(
                a.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0));
        mStartDestIdName = getDisplayName(context, mStartDestId);
        a.recycle();
    }

父类的onInflate

    public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
        final TypedArray a = context.getResources().obtainAttributes(attrs,
                R.styleable.Navigator);
        setId(a.getResourceId(R.styleable.Navigator_android_id, 0));
        mIdName = getDisplayName(context, mId);
        setLabel(a.getText(R.styleable.Navigator_android_label));
        a.recycle();
    }

父类的onInflate解析得到android_id和android_label,NavGraph的onInflate解析得到mStartDestId。

回到inflate的注释4处,while循环可是解析根节点navigation下的所有子节点,并且将子节点存入mNodes中。

以上整个解析xml的过程就结束了。

我们在回到NavController的setGraph方法内

    public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
        ...
        mGraph = graph;
        onGraphCreated(startDestinationArgs);
    }

  private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
        ....
        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);
            }
        }
    }

app刚启动的时候,没有mNavigatorStateToRestore和mBackStackToRestore,所以这里省略前边的内容。而且也没有deepLinke,所以执行navigate方法。

private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        ...
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                node.getNavigatorName());
        Bundle finalArgs = node.addInDefaultArgs(args);
        NavDestination newDest = navigator.navigate(node, finalArgs,
                navOptions, navigatorExtras);
        ...
    }

根据前面的分析整个是navigator是NavGraphNavigator。

    public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
        int startId = destination.getStartDestination();
       ...
        NavDestination startDestination = destination.findNode(startId, false);
       ...
        Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
                startDestination.getNavigatorName());
        return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
                navOptions, navigatorExtras);
    }

根据startId找到目标NavDestination,根据startDestination.getNavigatorName() (是fragment)

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        ...
        final Fragment frag = instantiateFragment(mContext, mFragmentManager,
                className, args);
        frag.setArguments(args);
        final FragmentTransaction ft = mFragmentManager.beginTransaction();
        ...
        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);
        ...
        ft.setReorderingAllowed(true);
        ft.commit();
        // The commit succeeded, update our view of the world
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }

navigate方法内部使用反射创建fragment对象,然后调用ft.replace(mContainerId, frag);添加frag。

至此整个导航过程都分析结束了。

onCreateView

 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        FragmentContainerView containerView = new FragmentContainerView(inflater.getContext());
        containerView.setId(getContainerId());
        return containerView;
    }

当NavHostFragment没有配置导航xml的时候,默认显示FragmentContainerView

onViewCreated

   public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ...
        Navigation.setViewNavController(view, mNavController);
        ...
    }

Navigation#setViewNavController

  public static void setViewNavController(@NonNull View view,
            @Nullable NavController controller) {
        view.setTag(R.id.nav_controller_view_tag, controller);
    }

NavHostFragment跟视图view设置tag,以后我们调用Navigation.findNavController(it), 内部会通过递归找到根视图view 的tag即controller。

相关文章

网友评论

      本文标题:Navigation的源码解析

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