ARouter开源框架详解

知识点汇总:

一:ARouter开源项目概述

二:关联知识点汇总与项目中的使用详解

三:ARouter实现原理分析

四:项目中的其他知识点汇总

五:扩展阅读


一:ARouter开源项目概述

解析:在项目不断的迭代,不断的添加功能时,项目的代码不可避免的会越来越庞大,而在项目的不断迭代中,高类聚、低耦合、易复用的思想需要不断的牢记在心,并需要很好的实现在项目中,而组件化的项目架构,就可以很好的实现项目代码根据业务模块进行解耦的实现,并且可以让项目更好的实现多人的并行开发,各个模块可以单独运行,从而提高项目的整体开发效率,下面就从ARouter项目源码的整体架构设计,一步步的了解该项目的实现吧。

项目结构:

App模块:主要内容是ARouter框架函数接口的使用示例,即其他组件模块已经使用ARouter相关路由等相关注解配置,通过该模块的接口实现界面的跳转、参数的传递、拦截器的使用等操作接口。

module-java模块:主要包含项目中的注解的使用示例,包含@Autowire、@Route、@Interceptor等注解在不用场景下的使用,并包含对外提供服务接口IProvider接口的实现类。

module-java-export模块:主要是包含提供服务接口IProvider接口的实现类和部分需要序列化传递的对象类。

module-kotlin模块:kotlin的注解使用示例代码,代码量较少。

arouter-annotation模块:项目中使用到的自定义注解(@Autowire、@Route、@Interceptor),并包含解析路由时需要的实体类和辅助类(RouteType、TypeKind(参数传递类型)、RouteMeta(路由信息实体类)、TypeWrapper)。

arouter-api模块:项目的主要实现代码,包含了Router表初始化,路由具体逻辑等实现,还定义了路由实现需要用到的相关实现的服务类和生成代码的接口,代码中涉及物流中心类(LogisticsCenter)、仓库类(Warehouse)、明信片类(Postcard)相关的概念,顾名思义的了解相关类在项目中的作用,可以更加容易理解项目的设计思想。

arouter-compiler模块:该模块的代码主要是通过APT(Annotation-processor-tools)注解处理器技术实现对项目中的自定义注解进行处理,并通过javapoet代码生成技术框架,实现对相关注解动态生成路由表注入等代码到build文件夹下面,另外还可以通过gradle文件的配置决定是否需要生成相关的路由表json信息文件。

arouter-gradle-plugin模块:该模块的出现主要是解决原来项目中是通过classUtils的getFileNameByPackageName()函数,通过指定包名,扫描dex包下面包含的所有的ClassName,从而实现路由表的初始化注入的,但是这种方式的效率太低的,需要遍历项目中的所有类,并提取出路由表生成的相关类,但这个模块通过gradle-plugin插件技术(grovvy)和ASM字节码生成技术实现在打包生成apk时,在LogisticsCenter类的loadRouterMap()函数动态添加生成的路由类的注入的代码,从而实现项目路由表、拦截器等信息的初始化,数据存放到Warehouse类里面。

arouter-idea-plugin模块:该模块的代码主要是实现AS开发平台的插件代码,实软件辅助插件:ARouter Helper的代码实现,源码中只包含一个类:NavigationLineMarker(kotlin实现),并引入了org.jetbrains.intellij相关源码类库。


二:关联知识点汇总与项目中的使用详解

2.1、反射与Hook技术在项目中的使用

2.2、自定义注解和APT注解处理器

2.3、JavaPoet动态生成代码实现

2.4、Gradle插件实现原理(grovvy实现)

2.5、ASM字节码生成技术实现

附加:对于项目中使用到的技术,我们要有具有举一反三的能力,这样在遇到相关的场景时,我们才能更好的根据之前积累的知识,实现新项目中的需求实现,从而让自己可以触类旁通。


2.1、反射与Hook项目中的使用

解析:先汇总一下项目中使用到的反射与hook相关的代码:

1、AutowiredServiceImpl类:

doInject函数:

getSyringe函数:

解析:

1、第一段代码的实现是当前类的父类不为空时,且父类并不是andorid系统包下面的类时,执行doInject函数 ,doInject这里是一个递归函数。 

2、第二段代码的通过类名和反射相关接口,创建相关类的对象实例。


2、LogisticsCenter类的register函数:

解析:通过类名和反射相关接口,创建相关类的对象实例,并判断创建的对象实例是否实现了IRouteRoot、IProviderGroup、IInterceptorGroup相关接口。


3、LogisticsCenter类的init函数:

解析:

1、通过类名和反射相关接口,创建相关类的对象实例,并调用对象的函数。


4、_ARouter类的attachBaseContext函数(包含反射和hook实现)

InstrumentationHook类的newActivity函数:

解析:通过invoke函数获取到ActivityThread类的示例,然后hook了ActivityThread类对象的属性类mInstrumentation,并通过自定义类InstrumentationHook(继承Instrumentation类),最终实现了对Instrumentation类的newActivity函数的hook,然后通过对newActivity的hook操作,实现了对即将跳转的activity执行类似autowire参数的赋值。


5、ClassUtils类tryLoadInstantRunDexFile函数:

解析:代码的作用是通过反射执行Paths类的getDexFileDirectory函数,获取到dex文件的存放目录,然后遍历相关目录,拿到所有dex文件的路径,并保存起来。

备注:getMethod()调用公共方法,不能反射调用私有方法,后来找到 getDeclaredField()能够访问本类中定义的所有方法。

总结:上面展示了项目中反射和hook相关的代码,下面我们顺便看看依赖库中反射的相关的类,下面目录中,除了Class类不在当前目录上,我们可以看看反射相关的其他类,并在需要的查看相关的类作用。


2.2、自定义注解和APT注解处理器

概述:在自定义注解的时候,我们需要先了解元注解有哪些,他们的作用是什么,下面我们一起看看java依赖库中都有哪些注解相关的类,项目中的常用元注解是Target和Retention注解,从名字可以大概猜测到target是指注解使用的地方(类、函数、属性、包名等),Retention是保留的意思,分别对应:编译前(java)、编译后(class)、运行时(Runtime)。

APT(Annotation Processing Tool)即注解处理器:是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成java文件作为输出,ARouter中通过处理注解生成相关路由信息类。 

     下面是本人根据项目绘制的注解处理器继承结构图:

备注:涉及相关辅助类:ProcessingEnvironment和RoundEnvironment,在BaseProcessor中,通过ProcessingEnvironment类对象获取相关注解操作的工具类和错误打印等类信息,RoundEnvironment类则是作为注解处理器的核心函数process参数出现,辅助解析注解配置的相关数据。

下面我们看看注解处理器的核心函数代码:

备注:这里我们需要了解到,这里routeElements是项目中所有Route注解的类集合,下面的具体的对routeElements的解析,需要通过解析出来的数据,通过javaPoet实现代码的动态生成逻辑,该部分会在后面讲解。

下面我们还是看看注解处理器相关的类库:

2.3、JavaPoet动态生成代码实现

解析:javapoet其实是square公司开源的项目(https://github.com/square/javapoet),主要实现代码动态生成的 的开源项目,下面是该项目的官方概述:

下面是该类库相关类:

下面我们通过具体的代码看javaPoet是如何通过调用接口生成相关代码的,先看看具体生成的代码示例:

解析:我们从上面的生成代码中,要了解各个生成的代码部分分别是通过javapoet的什么代码,什么接口生成的,


我们可以先记录下自己的相关疑问,例如:

1、生成的类的顶部依赖包是否需要设置

2、函数的注解是什么生成的

3、类名的设置

4、继承什么的抽象类,什么接口

5、重载的函数的命名与函数参数设置

6、函数生成的代码如何调用相关接口

7、函数的实现代码怎么设置


重载的函数的命名与函数参数设置:

Map>

Map

把相关的参数进行设置:

设置重载函数的实现代码:

函数生成的代码如何调用相关接口:

路由表注入文件生成的实现:

2.4、Gradle插件实现原理(grovvy实现)

解析:首先通过上面的分析已经了解到,该模块的代码是为了解决ARouter在初始化时,使用ClassUtils遍历项目 所有dex文件获取路由表注入文件导致的效率低下问题,故通过gradle plugin + ASM技术,实现对LogisticsCenter 类的loadRouterMap()函数添加路由表注入代码调用,从而提高路由表的注入效率,下面我们要了解Gradle插件需要引入相关的依赖库,并可以在外部依赖中查看相关库代码:

接着我们看实现的代码的入口类代码:

解析:通过代码我们了解到,首先判断该插件要是运行的app才会执行,然后就是扫描获取项目中的所有实现了相关接口的类的集合(IRouteRoot、IInterceptorGroup、IProviderGroup),这里请注意扫描的代码在RegisterTransform类的transform函数中,下面是扫描的相关代码。

解析:这里就是通过gradle插件找到项目中所有需要注入的动态生成的文件,接着就是需要通过ASM技术实现对扫描到的文件进行字节码写入操作,写入的操作主要实现在RegisterCodeGenerator类。


2.5、ASM字节码生成技术实现

解析:ASM 库是一款基于Java字节码层面的代码分析和修改工具,ASM 可以直接生产二进制的class文件,也可以在类被加载入JVM之前动态修改类行为,在ARouter中用于arouter_register插件插入初始化代码,项目中该代码的实现部分是使用grovvy语言实现的,下面先看看ASM的依赖库和我们具体所用到的。

下面看看字节码生成的具体实现代码:

解析:其实上面的四段代码的大概意思是在项目中找到LogisticsCenter类的loadRouterMap函数,最后通过MethodVisitor类的visitMethodInsn函数实现路由表字节码注入实现。


三:ARouter实现原理分析

项目重点类简介:

1、ARouter :api入口类。

2、LogisticsCenter :路由逻辑中心维护所有路由图谱。

3、Warehouse :保存所有路由映射,通过他找到所以字符串对应的路由信息。这些信息是在ARouter初始化的时候填充进来的。

4、RouteType :路由类型,现在有: *ACTIVITY,SERVICE,PROVIDER,CONTENT_PROVIDER,BOARDCAST,METHOD,FRAGMENT,UNKNOWN。

5、RouteMeta:路由信息类,包含路由类型,路由目标类class,路由组group名等信息。

      通过第一节的内容对ARouter项目的代码的整体模块分析,排除掉代码接口的使用示例和注解的使用示例,并且去除关于AS插件的实现模块,核心模块其实只有arouter-annotation模块、arouter-api模块、arouter-compiler模块、arouter-gradle-plugin模块这四个模块,下面就把项目运行逻辑一步步的梳理一遍。

路由运行的整体流程:

步骤一:在组件化项目中,对需要路由跳转的类添加相关注解@Router,同时需要传递的参数可以在目标类使用@autoware,当然这里也包括对外提供服务的IProvider接口的实现类和拦截器类的实现。(参见module-java模块、module-java-export模块示例代码)

步骤二:通过arouter-compiler模块相关代码,使用APT注解处理器和Javapoet动态生成解析上面步骤使用的注解,并在build模块生成相关的路由注入实现类,这部分类的生成,主要是要填充仓库类Warehouse的数据内容,生成代码入下图所示,docs文件夹下的json文件,是对路由表的json直观展示,gradle有相关配置是否生成。

步骤三:arouter-api模块中,ARouter单例类需要做初始化操作,由于使用的外观模式,相关逻辑实现在_ARouter实现类中,而ARouter的核心初始化逻辑主要在LogisticsCenter(物流中心)类的init函数中,而ARouter初始化主要就是对步骤二中生成的路由注入类的调用,从而实现Warehouse仓库类相关路由、拦截器等数据的填充,而这里框架中实现了有两种方式给接入者调用:

第一种方式:

使用classUtils类的getFileNameByPackageName函数,通过指定包名,扫描包下面包含的所有的ClassName,获取所有生成路由、拦截器等类名集合,然后在调用相关类实现路由、拦截器数据的填充,该方式的执行效率比较低,故后期添加了第二种实现方式。

第二种方式:arouter-gradle-plugin模块的实现,通过gradle plugin插件技术 + ASM字节码生成技术,在apk生成前,在LogisticsCenter类的loadRouterMap()函数中,动态生成相关代码,从而实现Warehouse仓库类相关路由、拦截器等数据的填充,下面是相关类的代码和注释:

备注:相关插件的引入需要在配置文件中设置:

项目的build.gradle文件中添加:

classpath "com.alibaba:arouter-register:$arouter_register_version"

子模块build.gradle中添加:

apply plugin: 'com.alibaba.arouter'

步骤四:通过上面的三个步骤,已经得到的项目中各个模块的代码相互跳转的路由表信息、拦截器、对外暴露的服务等数据的初始化,都存放在Warehouse仓库类的数据对象中,这么接着就是ARouter是如何通过路由相关信息的使用,从而实现不同界面的跳转逻辑,我们先看一下常规的界面跳转调用代码示例:

ARouter.getInstance()

.build("/test/activity2")

.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)

.navigation(this);

       代码主要通过单例模式、外观模式和链式调用实现,通过对build函数的返回对象,我们看到了一个新的类明信卡Postcard类,这个类继承自路由信息存放类RouteMeta,而这里路由信息都存放在Warehouse仓库类中,而Postcard对象的相关数据的初始化,恰恰就是通过仓库里面的数据进行赋值的,下面我们分别看看两个类的部分源码实现:

        通过上面对两个类的源码,我们大致了解了两个类存放的相关数据信息,那现在重点就是这些信息都是在什么时候使用的和如何使用,路由的跳转逻辑调用时会执行到Postcard的navigation函数,而随着对函数的进一步查看,其实最终的跳转逻辑是在_ARouter类的_navigation函数中实现activity的跳转的,并且也是通过startActivity或 startActivityForResult进行跳转的。


通过上的项目整理执行逻辑的四个步骤的描述,大致应该对项目的整体执行有个大致了解了,这里简单的总结一下上面的描述:

1、使用ARouter项目中提供的自定义注解,对需要的类和对象添加相关注解。

2、运行项目程序,在使用了相关注解的模块中,生成因为相关注解而动态生成的路由表、拦截器等类注入文件。

3、ARouter初始化时,加载动态生成的路由表、拦截器等类信息,从而实现不同模块路由表的初始化数据。

4、通过ARouter单例类的接口调用,从而利用上面路由表、拦截器等相关数据,从而实现路由的跳转。


四:项目中的其他知识点汇总

4.1、如何获取dex包中所有实现特定接口相关实现类集合

4.2、实现自定义线程池

4.3、同步关键字synchronized与wait(),notifyAll()的使用

4.4、CountDownLatch与CancelableCountDownLatch的使用

4.5、生成路由文档arouter-map-of-modulejava.json的配置与原理

4.6、activity在解耦情况下,跳转的实现方案

4.7、项目中还有什么模块可以进一步完善的


4.1、如何获取dex包中所有实现特定接口相关实现类集合

解析:这段代码在ClassUtils类的getFileNameByPackageName函数中,通过getSourcePaths函数获取项目中所有的dex文件路径,然后在子线程中获取各个dex的所有类文件,根据包名前缀找出所有路由表注入文件。

4.2、实现自定义线程池

解析:自定义线程池,我们需要了解线程池的各个参数应该如何填写,及各个参数的作用,这里主要实现自己复杂的是自定义线程工厂。

4.3、同步关键字synchronized与wait(),notifyAll()的使用

解析:这里用到了对象锁和基类对象的notifyAll()和wait()函数,拦截器需要在子线程中进行初始化时,这是如果拦截器还没初始化成功,主线程就设置处于等待状态,直到拦截器在子线程初始化成功,并调用notifyAll()函数解除主线程的等待状态。


4.4、CountDownLatch与CancelableCountDownLatch的使用

解析:上面是项目中继承CountDownLatch类,并只是简单的实现了一个新函数cancel,在cancel执行时,相当于直接设置countDown值为0,下面看看

CountDownLatch类官方注解:

解析:同步援助允许一个或多个线程等待其他线程正在执行的一组操作完成,看看项目的使用代码。

解析:可以看到项目中使用CountDownLatch,是对跳转时需要对所有的拦截器的处理都完成了,或者有拦截器出现异常了,还会执行cancel操作,使程序继续执行下去。

4.5、生成路由文档arouter-map-of-modulejava.json的配置与原理

解析:下面看看生成的代码,下面代码的数据部分主要为Map> docSource = new HashMap<>();,

项目中在上下代码中初始化了该散列表数据。

4.6、activity在解耦情况下,跳转的实现方案

解析:

方式一:使用隐式启动,这种方式如果跳转的界面很多,对配置文件和跳转逻辑的处理会很麻烦。

方式二:使用反射,如果所有界面跳转都使用反射,对项目的整体性能有影响。

方式三:使用路由表。

4.7、项目中还有什么模块可以进一步完善的

解析:

一:项目中目前只是对Activity、IProvider和Fragment进行处理,可以考虑自己实现其他RouteType类型。

二:项目中其实还没有对相关的外部提供的服务提供实现代码,如果项目中有相关需求,可以考虑实现以下相关接口类的实现:

2.1、PathReplaceService      //路由自定义处理替换

2.2、DegradeService          //没有找到路由的通用回调

2.3、PretreatmentService     //navigation 预处理拦截


五:扩展阅读

1、https://www.jianshu.com/p/bc4c34c6a06c(可能是最详细的ARouter源码分析)

2、https://race604.com/annotation-processing/(Java注解处理器)

3、https://github.com/square/javapoet(更方便生成的Java代码的库)

4、https://blog.csdn.net/zhutulang/article/details/48504487(CountDownLatch理解一:与join的区别)

5、https://asm.ow2.io/index.html(ASM官方文档)

6、https://www.jianshu.com/p/99c8e953654e(如何调试Gradle Plugin)

7、https://www.jianshu.com/p/da771b7e8add(【安卓进阶】 这次我把ARouter源码搞清楚啦!)

8、https://www.jianshu.com/p/37fdbc6c0969(探索 ARouter 原理)

9、https://juejin.cn/post/6896019556918755341(字节码插桩技术-ASM)

10、https://blog.csdn.net/hejjunlin/article/details/53454514(在线看Android系统源码,那些相见恨晚的几种方案)

11、https://github.com/alibaba/ARouter(项目源码地址)

你可能感兴趣的:(ARouter开源框架详解)