[置顶] 路由方案之ARouter源码分析

目录

  • 前言
  • 利用Intent跳转
  • 利用scheme完成跳转
  • 正式开始ARouter的探索
  • 小结

前言

去年底公司内部作技术分享的时候,笔者分享过一次ARouter源码的分析,最近在整理草稿箱里的内容,顺便将它分享出来,记录一下。

利用Intent跳转

我们平常开发过程中,会有各个页面的跳转情况,比如LoginActivity跳转到
HomeActivity的时候,我们最常见的使用方式可能如下:

Intent intent = new Intent(context,HomeActivity.class);
intent.putExtra(key,xxx)
startActivity(intent);

这种方式在单工程单组件开发模式下是没有问题的,但是如果项目规模逐步增大,向组件化开发方向逐步演进的时候,这种方式会有很多的坑,最明显的一点就是各个组件会有其他模块的强依赖。
为了避免这种尴尬的问题出现,我们势必要寻求一种可以解耦组件之间的依赖,或者说尽可能减少组件之间的依赖,方法肯定是有的,聪明的你肯定会马上就想到,可以用隐式调用啊:

使用系统的隐式调用方法,在对应的清单配置文件里配置好相应的action,可以跨组件完成页面的跳转和数据的传递

但是你有没有想过一个问题,随着项目的逐渐增大,页面越来越多,相应的配置会逐步增多,无疑会增大项目的维护成本,只能说这是一种解决方案,但并非最优的方案。

利用scheme完成跳转

基于前一种方案的增强方案,清单里配置大量的配置,不好维护,觉得麻烦,那也行,我们可以使用一个统一的代理ProxyActivity,将大小设置为1像素,主题透明,所有的数据都通过scheme完成指定页面的跳转。

这种方式解决了前面的需要注册大量的清单配置,所有的跳转都通过这个类作为入口,处理完成之后关闭该页面,神不知鬼不觉,但是,
又有个新问题,就是它和我们的各个跳转的模块耦合在一起了,换句话说就是不够通用,如果跳转参数变了,我们需要到这里来重新更改跳转参数,如果app内跳转页面非常多,这里要改动也很多,所以多工程多组件话的嵌套依赖问题还没有得到根本的解决。

思考: 那如何设计一个通用的与业务无关的跳转组件?

正式开始ARouter的探索

上面说了很多,到这里才是正题,为了寻求更通用的,跟业务无关的方式来处理这种问题,我也Google了一些轮子,找到了阿里开源的轮子ARouter(这个上期分享组件化开发的同学已经提到过了),下面我们来探索一下阿里的源码中这个轮子是如何实现的,但是我个人实际上是不愿意引入太多的源码的内容,第一是容易让人昏昏入睡,第二是我更喜欢用画图的形式直观的表现出来。

ARouter的三个核心模块:

//编译处理器
arouter-compiler
//核心Api
arouter-api
//注解
arouter-annotation

第一个模块arouter-annotation

这个模块的内容不多,主要用来存放一些注解和枚举类型的文件,大家心里有个大概的印象即可,后续可以根据图示回到此处来看各个文件的作用。

文件名 描述
Route.java 路由注解,用来标注路由路径,分组,优先级等信息
Autowired.java 自动装配注解,用来注入参数
Interceptor.java 拦截注解,用来标注拦截优先级等信息
RouteMeta.java 路由最基本的数据信息,包括路由类型,目标Class,路由路径,路由分组,优先级,额外信息,参数类型等
RouteType.java 路由类型,有Activity,Service,ContentProvider,Fragment等等

第二个模块arouter-compiler

这个模块的主要作用是在编译期间,过滤第一个模块中定义的几个注解(Route,Autowired,Interceptor),然后生成编译期间的中间代码

文件名 描述
RouteProcessor.java 路由处理器
InterceptorProcessor.java 拦截处理器
AutowiredProcessor.java 装配处理器
TypeUtils.java 类型交换工具类

我们还是从编译期间开始,一个一个来看:

RouteProcessor

主要在编译器间获取Route注解的类,生成路由表,主要方法如下:

//解析路由元素
private void parseRoutes(Set routeElements);
//路由元数据进行分类
private void categories(RouteMeta routeMete);
//校验路由元数据中的路径和分组信息
private boolean routeVerify(RouteMeta meta);

简单归纳了一个图如下:

根据图上的信息(建议读者区调试一下ARouter的源码,其实代码量也不大)可以看到,在编译期间,RouteProcessor中的process方法会获取到环境变量中过滤的元素集合,由parseRoutes方法解析并
生成编译期间产生的中间类,命名规则就是工程名+$$+Group+$$+模块名

1.根据不同的RouteType来构造不同类型的RouteMeta
2.对生成的RouteMete进行分组存入groupMap中
3.遍历这个groupMap,使用JavaPoet的语法将中间类文件写入build目录下

InterceptorProcessor

它的作用主要是在编译器编译期间生成拦截器的相关代码(因为我们跳转的页面可能需要登录)
主要方法

//解析拦截器
private void parseInterceptors(Set elements);
//简要拦截器元素
private boolean verify(Element element);

AutowiredProcessor

//遍历标注了Autowired的类
private void generateHelper();
//构建一些声明
private String buildStatement(String statment, int type, boolean isActivity);
//对自动装配的字段进行分类
private void categories(Set

第三个模块arouter-api

在讲第三个模块之前我并不想直接怼源码,换种思路,相信我们在了解一个好用的轮子之前,通常都是从使用开始的,那我们就从使用入手,带着疑问,进行探索:

1.ARouter.init();
2.ARouter.getInstance().build(“/fungo/home”).navigation();
引入相关的依赖和标记注解我就不说了,使用上来说就这两步就可以完成一个基本的路由跳转,简单粗暴。
从第一个步骤开始:初始化到底干了些什么工作呢?不初始化可以直接调用吗?不废话,直接进源码


可以看到真正的实现并不是它自己,而是_ARouter,其他方法也基本都是代理类_ARouter来实现的。

通过_ARouter.init来完成初始化
初始化之后又调用了_ARouter.afterInit();来完成初始化之后的的工作
看来还得继续深挖,我们进入_ARouter.init的内部

本着刨根问底的精神我又钻进了LogisticsCenter.init,我想看看它到底有多少层,到底做了些什么?

上图是我断点调试的截图,至此我们基本明白_ARouter.init的工作内容:

首先由ClassUtils扫描dex文件获取编译期间生成的中间类集合,这个工具类就不往里面看了
根据Root,Interceptors,Providers来进行分组,遍历上一步返回的中间类,通过反射完成实例化,并将路由元数据存入Warehouse中。

再来看_ARouter.afterInit的内部,这一步是在_ARouter.init完成之后进行的


再跟

这里可以很明显看到是实例化了实现了InterceptorService接口的InterceptorServiceImpl类,关于拦截器内部实现等会再说。
经过上面的几步,相信大家基本已经了解了,下面整理一下初始化的流程:

1.由LogisticsCenter层初始化编译期间产生的中间类,并分组,加载到内存缓存Warehouse中
2.实例化拦截器

现在我们已经明白了初始化ARouter做了哪些事情,接下来我们看看标准的路由请求做了哪些动作,由于ARouter的实现基本都是由_ARouter完成,所以接下来我们直接看_ARouter中对应的方法即可。


先看build做了哪些操作:

1.根据传递path信息,分离出对应的group组,然后调用重载的方法,返回一个Postcard(继承自RouteMeta)对象,内部包含的是路由路线信息,比如我要跳转的页面,启动的标识,Activity进出动画,以及是否需要拦截等信息
它通过链式调用的方式来组装好参数(这些参数包括基本类型和普通类)
2.当Postcard组装好需要传递的参数以及其他相关信息之后,会调用navigation方法开始正式发起路由请求

Postcard内部的navigation方法其实也是调用_ARouter来实现的,所以我们直接来看_ARouter中的navigation即可,再次进入_ARouter内部看看navigation做了哪几个关键的动作,方法内部内容我画了一个大致的流程图,帮助理解

综合源码和流程图我们看可以整理出基本的处理动作

1.LogisticsCenter.completion(postcard);对传入的参数进行处理,成功继续向下执行,失败则调用降级处理回调方法,继续向下执行
2.判定是否需要拦截这个调用,如果拦截被中断则回调中断方法,否则继续执行_navigation方法,这个方法也基本到了最后一步了
3._navigation内部,会根据Postcard中的类型分别处理,它会先包装Intent参数,然后根据requestCode来决定是使用startActivityForResult还是startActivity来跳转到对应的Activity中去,最后处理转场动画。

LogisticsCenter.completion(postcard)处理流程

现在构建标准路由的请求流程大致清楚了,但是还是有几点不明白的地方?

1.我们的参数是如何传递的,普通实体类对象序列化如何处理?
2.我们传递到目标中的参数是如何实现自动装配的?

所以我们接下来要关注的就是内部的具体实现了,先看第一个问题,要知道基本的数据类型都是支持序列化的,如何将我们的实体类对象传递过去,我们首先要做的就是自己实现SerializationService接口。

是的,很聪明,官方给出的具体实现类是JsonServiceImpl,其实就是先将实体对象转成json字符串,等到达路由目标的时候在反转成该对象。

现在普通实体类的序列化我们清楚了,那么是如何实现到达路由目标的时候,自动装配呢?其实是在目标类中调用ARouter.getInstance().inject(this);完成注入

还是得进入源码内部看下

可以看到,在目标类中调用inject方法,会通过获取自动装配服务的实现类AutowiredServiceImpl来完成注入,再进入AutowiredServiceImpl中,断点查看就比较清晰了,首先查询缓存中是否有标注了Autowired的类存在,如果不存在则通过反射的方式实例化xxx$$ARouter$$Autowired这个类,调用其inject的方法将目标比如HomeActivity传入。

所以接下来我们还要进入xxx$$ARouter$$Autowired这个类中看看情况
进入HomeActiivty$$ARouter$$Autowired的类,该类实现了ISyringe接口的inject方法,该方法做的事情,就是获取Activity中Intent内的值(我们的inject方法实在onCreate中调用的,自然可以拿到Intent的内容),然后赋值给标注了Autowired的字段

直接赋值,有木有!!!
这样以来,我们梳理一下流程

1.发起路由请求会先通过LogisticsCenter.completion自动包装好Postcard的参数
2.然后会将Postcard中的数据传入到Intent中,唤起目标Activity
3.目标Activity的onCreate会调用inject方法(自动装配处理器编译期间产生的中间类(xxx$$ARouter$$Autowired,来完成赋值操作)

下面几个是核心的实体类

1.Postcard一个包含路由路线的容器,贯穿整个路由请求,通过链式调用的方式来设置需要传递的参数,包含常用的数据类型和Activity的进出动画以及启动标识,类似于信使的作用
2.Warehouse内存缓存类,刚才分析的时候也看到了,ARouter初始化的时候会将编译期间生成中间类通过反射的方式加载进来,此时就是通过Warehouse保存的,它缓存的有几样内容

1.路由元数据
2.节点数据
3.拦截器数据

到这里就完结了吗?通过刚才的分析和流程图,我们基本了解了整个路由请求的过程,貌似我们还是遗漏了一点,那就是跳转拦截!这也是ARouter的很核心的功能之一,
拦截内部的实现原理,可以对比源码和参考的流程图

官方的架构图

小结:
整个ARouter的源码设计还是很清晰的,内部的设计模式运用也很灵活,对大型项目的扩展方面思考很细致,笔者能力有限,读者如果觉得上述讲的不够明白或者有错误的地方可以联系笔者,强烈推荐您Github上下载最新的ARouter源码,进行调试阅读,不过最新的ARouter源码部分Api的细节可能有变动。

参考:
https://yq.aliyun.com/articles/71687

猜你喜欢

转载自blog.csdn.net/byhook/article/details/79945460