组件化

327 阅读8分钟

1 组件化介绍

1.1 什么是组件化

各业务组件不横向依赖,可独立运行。

1.2 组件化意义

各业务组件不相互依赖,可以相互交互,可任意组合、自由拆卸、重复利用,高度解耦,分层独立化。

2 组件化Gradle配置

  1. 统一管理依赖。
  2. 抽取base gradle文件,把gradle的一些公共配置,写到base里。
  3. 在组件的build.gradle中动态切换library与application,并在application组件中定义applicationId。
  4. 配置组件独立运行时的AndroidManifest文件,非独立运行时,打包需要排除的文件。
  5. 对业务组件的依赖,使用runtimeOnly,实现代码隔离。
  6. build.gradle文件添加resourcePrefix,实现资源隔离,资源图片手动添加前缀。

implementation、runtimeOnly、api区别

  • implementation 不对外开放,只是本module依赖.
  • runtimeOnly 运行时才依赖
  • api 可以传递依赖,别的项目也可以依赖api的jar包.

3 组件模块初始化

组件在独立运行时,有单独的manifest,当然也就可以指定Application类进行初始化。组件进行合并的时,Application只能有一个,并且存在宿主App中,组件该如何进行初始化? 通过反射解决该问题(动态代理模式):

  1. library-base定义组件生命周期管理类,注册需要初始化的组件类名全路径并定义单例类ModuleLifecycleConfig,遍历组件生命周管理类中注册的组件初始化路径,通过反射动态调用初始化方法。
  2. 定义IModuleInit接口,需要初始化的组件实现该接口,并在实现类中做初始化工作。
  3. 在宿主模块的Application中调用调用ModuleLifecycleConfig的相关方法。

4 添加调试(模块单独运行)代码

需要独立运行的模块添加调试Activity、AndroidManifest等。

5 组件间通信

组件间是完全无耦合的存在,但是在实际开发中肯定会存在业务交叉的情况,通过Arouter实现组件间通信。

6. Arouter

6.1. APT

APT的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。

常见注解框架:

  • Eventbus:拼接代码,使用IO(BufferWriter)写入文件。

  • Butterknife

  • Arouter:使用JavaPoet、Auto-Service

    • JavaPoet:square推出的开源java代码生成框架,提供生成Java文件的API。创建方法、创建类、方法加入类、创建包,类加入包等一系列的API。
    • Auto-Service:提供了简便的方式去注册APT,避免了原本繁琐的注册步骤
  • DataBing

6.2 ARouter的技术方案

ARouter提供了两个SDK,分别是面向两个不同的阶段。

  1. arouter_api:是面向运行期的,提供api,以实现路由功能。
  2. arouter_compiler:是作用于编译期的,包含了注解处理类,通过java的Annotation Processor Tool按照定义的Processor生成所需要的类。 其中arouter_annotation:包含了注解类以及携带数据的bean

6.2.1 页面注册

流程:

  1. 通过注解处理器扫出被标注的类文件
  2. 按照不同种类的源文件进行分类:ARouter是一个框架,其能够提供的功能非常多,所以不仅仅提供了跳转功能,它也能够实现模块之间的解耦,除此之外ARouter还能够提供很多的功能,像刚才提到的拦截器可以实现自动注册,其实ARouter中的所有组件都是自动注册的
  3. 按照固定的命名格式生成映射文件
  4. 初始化的时候通过固定的包名加载映射文件 前三部发生在编译期,最后一步是运行期。

6.2.2 加载:分组管理,按需加载

ARouter对于注册的页面不会一次性加载出来,对性能影响恨大。故Arouter中提出了分组的概念,ARouter允许某一个模块下有多个分组,所有的分组最终会被一个root节点管理。如上图中所示,假设有4个模块,每个模块下面都有一个root结点,每个root结点都会管理整个模块中的group节点,每个group结点则包含了该分组下的所有页面,也就是说可以按照一定的业务规则或者命名规范把一部分页面聚合成一个分组,每个分组其实就相当于路径中的第一段,而每个模块中都会有一个拦截器节点就是Interceptor结点,除此之外每个模块还会有控制拦截反转的provider结点。

ARouter在初始化的时候只会一次性地加载所有的root结点,而不会加载任何一个Group结点。 下图中的三个圈中体现的就是ARouter初始化时加载的状况。

当某一个分组下的某一个页面第一次被访问的时候,整个分组的全部页面都会被加载进去,这就是ARouter的按需加载。

6.2.3 拦截器

6.2.4 InstantRun兼容

6.3 依赖注入的实现

6.4 Arouter源码分析

6.4.1 编译期

6.4.2.1 arouter-compiler

arouter-compiler通过自动配置的Processor, 查找声明了arouter-annotation里定义的 annotation, 然后根据annotation的配置使用javapoet生成辅助注入的代码。

arouter-compiler 有三个 Processor:

  • AutowiredProcessor -- 处理 @Autowired
  • InterceptorProcessor -- 处理 @Interceptor
  • RouteProcessor -- 处理 @Autowired,@Route
6.4.2.1.1 RouteProcessor

RouteProcessor.process中遍历所有声明@Router注解的类,使用javaPoet的接口,生成java代码,先创建方法、创建类、方法加入类、创建包,类加入包等一系列的API。

RouteProcessor 做了什么事情

  • 根据 @Route 配置 path, group 生成 ARouterRootRoot"moduleName"类。

    各模块route、Provider的root节点。

  • ARouterRootRootmoduleName.java 以 group 为 key, 注入了生成的 ARouterGroupGroupxxxx 类。

    各root节点的子节点。

  • 根据 @Route 配置 生成 ARouterProvidersProviders"groupName" 和 ARouterGroupGroup"groupName"

  • ARouterProvidersProvidersxxxx.java 以类为 key, 注入了 IProvider 接口的实现类

  • ARouterGroupGroupxxxx.java 以 path 为 key, 注入 RouteMate实例, 描述实现类和 @Autowired 参数描述

6.4.2.1.2 InterceptorProcessor

生成注入拦截器的代码:ARouterInterceptorsInterceptorsmodulename.java

把拦截器的类,以优先级为key,存入Map<Integer, Class<? extends IInterceptor>>中。

6.4.2.1.3 InterceptorProcessor

负责生成自动注入的代码

6.4.2 运行期

6.4.2.1 Init

调用ARouter.init初始化Arouter,ARouter.init,使用标志位,确保初始化代码执行一次,调用了_ARouter.init。

其中Arouter是对外暴露接口的类,_ARouter是实现类,此处使用的设计模式:

  • 装饰器模式:解耦,可以有选择性的暴露实现类的接口,也可以在实现类的基础上增加功能。
  • Arouter和_Arouter分别使用了单例模式。

LogisticsCenter.init(mContext, executor)是_ARouter.init的核心方法。executor是一个线程池,多个拦截器以及跳转过程中都是需要异步执行的。

LogisticsCenter.init:就是查找com.alibaba.android.arouter.routes包下的类(编译期通过分析注解生成的代码), 获取实例并强制转化成IRouteRoot, IInterceptorGroup, IProviderGroup, 然后调用 loadInto 方法:

  • IRouteRoot:将注册了声明 Route 注解的类添加到参数集合中,也就是groupsIndex。
  • IInterceptorGroup:将注册了Interceptor注解, 并实现 IInterceptor 接口的类添加到参数集合中,也就是interceptorsIndex中。
  • IProviderGroup:将注册了声明 Route 注解, 并实现了 IProvider 接口的类添加到参数集合中,也就是providersIndex中。 总结来说,init过程就是把所有注解的信息加载内存中(root节点),并且完成所有拦截器的初始化。

6.4.2.2 navigation

完成初始化之后,就可以实现路由跳转了:

ARouter.getInstance().build("/test/activity").navigation();

ARouter.getInstance().build返回PostCard对象

PostCard.navigation最终调用了_Arouter.navigation,内部调用了LogisticsCenter.completion,如果Warehouse未找到RoteMeta,会再次调用IRouteGrop的loadInto方法,重新加载缓存,并再次调用completion,对PROVIDER、FRAGMENT设置绿色通道。相当于是对group做了一次懒式加载。

继续_Arouter.navigation,PROVIDER、FRAGMENT是绿色通道,直接执行_navigation,其余的使用interceptorService进行拦截(doInterceptions)。

interceptorService是 ARouter 配置的默认的拦截服务,Arouter.init中调用_Arouter.afterInit初始化的。doInterceptions中递归调用_excute,所有拦截器都执行process。若都未拦截,执行_Arouter._navigation。

_Arouter._navigation:

  • activity:初始化Intent,Set flags,Set Actions,主线程启动Activity(handler实现),设置动画。
  • PROVIDER:返回provider。
  • FRAGMENT:返回fragment。

6.4.3 ARouter 总结

编译期 arouter-compiler 负责生成了注入的的一些代码:

  • ARouterGroupGroupgroup-name 以 group-name 为文件名注入该 group 下声明了 @Route 的信息
  • ARouterRootRootapp 按照 group 分组注入了 ARouterGroupGroupgroup-name
  • ARouterProvidersProvidersapp 注入实现 IProvider 接口的信息
  • ARouterInterceptorsInterceptorsapp 注入实现 IInterceptor 接口信息
  • Test1ActivityARouterARouterAutowired @Autowired 自动注入的实现

运行期ARouter 初始化的时候会把注入的信息进行缓存

在进行 navigation 的时候, 根据缓存进行懒加载, 然后获取实际对象或者跳转 activity.

自动注入就是调用 对应的 Test1ActivityARouterARouterAutowired 实例, 对声明 @Autowired 的字段进行复制操作.

6.4.4 Arouter初始化速度优化:

使用arouter-register插件。

  • 使用插件前:

    init中通过线程池把 dex 路径转换成 DexFile。

  • 使用后:

    Arouter自动加载路由表的插件是使用的通过gradle插桩技术在编译期插入代码来达到自动加载路由表信息。那么在 ARouter 初始化的时候就不会再去查找过滤相应的以 com.alibaba.android.arouter.routes开头的类名了,从而达到减少初始化时间的目的。