Jetpack Navigation工作原理

什么是Navigation?

在没有Navigation之前我们切换Fragment是通过FragmentManager的add、commit、replace等方法操作(网上有很多传统的Fragment的切换方法,可以自行查资料学习),在有了Navigation之后我们的切换逻辑就简单了,甚至可以通过IDEA的视图都可以配置切换跳转的功能。


新建一个项目、选择下图所示的模板:

该模板创建完成后 运行项目看到如下的效果图,并打开activity_main.xml




    

    


我们关注一下几个信息:

  • BottomNavigationView
    BottomNavigationView是一个底部导航栏控件,一般和fragment一起使用。

  • app:menu=”@menu/bottom_nav_menu”
    这个menu文件就是底部显示的icon和文字.



    

    

    




  • android:name=”androidx.navigation.fragment.NavHostFragment”
    NavHostFragment

    NavHostFragment是一个Fragment,其作用有两个,一个是给要显示的Fragment提供一个载体,还有就是控制页面导航行为

    它的onCreateView中创建了一个Fragment的布局容器


  • app:defaultNavHost=”true”
    是否加入回退栈
  • app:navGraph=”@navigation/mobile_navigation”
    关联navigation文件,



    

    

    


看完了这些基础的配置,就需要了解它们是怎么实现导航功能的呢?


还记得上面提到过的NavHostFragment吗?下面来看看它的源码.

这里先给出一个结论:在NavHostFragment中实例化了四个导航器,它们分别是:

  1. NavGraphNavigator
  2. ActivityNavigator
  3. FragmentNavigator
  4. DialogFragmentNavigator

它们的父类都是”Navigator”

看看Navigator的类结构:

NavHostFragment中onCreate函数

@CallSuper
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(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);
        ......

在NavHostFragment的onCreate中创建了一个NavHostController
mNavController = new NavHostController(context);
NavController的构造函数如下:

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

在看NavHostFragment类中onCreate方法里面的 onCreateNavController(mNavController);

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

从上面的代码就可以看出,通过NavHostFragment的onCreate 完成了上面提到的四个导航器的创建。
这四个导航器分别是为Fragment、Activity、DialogFragment导航的,NavGraphNavigator有点特殊我们放在最后讲。


接下来我们看看ActivityNavigator源码:

image.png

可以看到,ActivityNavigator继承了Navigator,并有一个Name注解。这几个注解就代表是为Activity提供导航的,注解的用途官方是这么解释的:

/**
* 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)
*/
每个Navigator子类都应该提供name注解,用于注册到NavigatorProvider中,这里可以把NavigatorProvider理解成HashMap,把name作为key,Navigator实例作为value存储起来,通过NavigatorProvider向外提供。

倒不妨去看看NavigatorProvider#addNavigator(Navigator)源码

image.png

getNameForNavigator(navigator.getClass())返回了name

addNavigator(name, navigator);完成了存储。

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

跟进方法getNameForNavigator(navigator.getClass())


    @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注解的值提取出来了。

接下来我们看ActivityNavigator中的createDestination方法。

   @NonNull
    @Override
    public Destination createDestination() {
        return new Destination(this);
    }

Destination里面最终是将ActivityNavigator的注解name存起来。后面我们可以看他他的用处。

比较核心的还是在navigate方法,接下来我们看它是怎么实现导航的:

  @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (destination.getIntent() == null) {
            throw new IllegalStateException("Destination " + destination.getId()
                    + " does not have an Intent set.");
        }
        Intent intent = new Intent(destination.getIntent());
          //  参数 flagsd等配置
   
            mContext.startActivity(intent);
        
         //动画配置

        // You can't pop the back stack from the caller of a new Activity,
        // so we don't add this navigator to the controller's back stack
        return null;
    }

删了打断代码,增加了两个注释。其实功能很简单,就是通过Intent来实现跳转的,中间做了一下参数设置,flags的添加,和动画等。让后调用 mContext.startActivity(intent);启动activity,

也就说说我们只要通过调用navigate方法就能实现Activity的启动.

说完了ActivityNavigate后我们来说NavGraphNavigator.,它不同于ActivityNavigator和FragmentNavigator.
接下来我们就看看它怎么个不同。

在NavGraphNavigator的createDestination方法中,它并不像其他几个Navigator是创建了Destination对象,
它创建的是NavGraph对象

image.png

进入NavController中:

image.png

接着进入inflate里面

 @SuppressLint("ResourceType")
    @NonNull
    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();
        }
    }

传进来的id就是 mobile_navigation.xml这个资源布局

image.png



    
        
        
        
    

  .........

    

inflate方法中就是解析xml文件,把每个节点信息解析出来存到final SparseArrayCompat mNodes = new SparseArrayCompat<>();这个里面

在回到NavGraphNavigator的navigate方法

 @Nullable
    @Override
    public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
        int startId = destination.getStartDestination();
        if (startId == 0) {
            throw new IllegalStateException("no start destination defined via"
                    + " app:startDestination for "
                    + destination.getDisplayName());
        }
        NavDestination startDestination = destination.findNode(startId, false);
        if (startDestination == null) {
            final String dest = destination.getStartDestDisplayName();
            throw new IllegalArgumentException("navigation destination " + dest
                    + " is not a direct child of this NavGraph");
        }
        Navigator navigator = mNavigatorProvider.getNavigator(
                startDestination.getNavigatorName());
        return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
                navOptions, navigatorExtras);
    }

可以看到NavGraphNavigator的navigate并没有真正的实现导航,而是通过 mNavigatorProvider.getNavigator()得到对应的导航器,做了一个对应多态调用,最后由对应的FragmentNavigator和AcitivtyNavigator等去实现导航,前面我们已经介绍了Actitivi的导航实现,下面在来看看FragmentNavigator怎么实现的导航。

 @SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
    @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();

     
      //动画代码

        ft.replace(mContainerId, frag);
        ft.setPrimaryNavigationFragment(frag);

    }

可以看到instantiateFragment(mContext, mFragmentManager,
className, args); 通过反射实例化一个Fragment,然后调用replace方法显示出来了,
这里使用replace导致每次切换都会重新创建Framgnt,所以效率比较低。后面我会自定义一个Fragment导航器,通过show方法控制fragment的显示。

  BottomNavigationView navView = findViewById(R.id.nav_view);
        final NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                navController.navigate(item.getItemId(), null);
                Log.d("navController", item.getTitle().toString());
                return true;
            }
        });

上面代码就是通过导航器的navigate方法实现了导航功能。

基本的导航器的使用和原理已经很清晰了,后面会讲一下如何自定Fragment的导航器。

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/19578,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?