Component Initializer(组件初始化器)开源项目 介绍
Component Initializer(组件初始化器)介绍
原理分析
刚开始我想到的方案是通过注解来实现。首先用@Component注解标记Component类,然后我们用注解处理器就可以拿到Component类的信息,然后利用注解处理器自动生成ComponentInitializer.java类,将收集到的Component类的初始化代码new Component(),添加到ComponentInitializer类中,这样就可以完成自动注册的功能。
看着方案很完美,实际做的时候发现一个问题,就是对于多module的形式,每个module都是独立编译的,都会独立编译成一个aar文件,这也就是为什么我们如果用annotationProcessor的时候需要在每个用到的module上都添加annotationProcessor。这样就会导致我们通过annotationProcessor只能拿到当前module中的Component类的信息。无法拿到用户在各个组件module中配置的所有的Component类的信息。
所以通过annotationProcessor来实现自动注册是行不通的。
那么有没有其他方案呢?
后来我在想,这个问题做框架的是都需要考虑的,那么其他框架是怎么解决的呢。Arouter是如何解决这个问题的?从Arouter的介绍中我发现它是通过AutoRegister来解决的,然后通过AutoRegister的实现原理,我发现它是通过Transform API的技术方案来解决的。
Transform API是从Gradle 1.5.0版本之后提供的,它允许第三方在打包Dex文件之前的编译过程中修改.class字节码文件,这样在这个过程中,我们就可以搜集到所有的Component类,然后通过修改ComponentInitializer.class字节码文件,就可以把注册的代码添加到ComponentInitializer.class字节码文件里。
除了Transform API的方案,其实还有其他两种技术方案。(以下来自AutoRegister作者的分析)
-
ARouter的解决方案。
AutoRegister在ARouter里是一个可选方案,ARouter本身还有一个解决方案:每个module都生成自己的java类,这些类的包名都是’com.alibaba.android.arouter.routes’,然后在运行时通过读取每个dex文件中的这个包下的所有类通过反射来完成映射表的注册,详见ClassUtils.java源码 -
ActivityRouter的解决方案(demo中有2个组件名为’app’和’sdk’):
- 在主app module中有一个@Modules({“app”, “sdk”})注解用来标记当前app内有多少组件,根据这个注解生成一个RouterInit类
- 在RouterInit类的init方法中生成调用同一个包内的RouterMapping_app.map
- 每个module生成的类(RouterMapping_app.java 和 RouterMapping_sdk.java)都放在com.github.mzule.activityrouter.router包内(在不同的aar中,但包名相同)
- 在RouterMapping_sdk类的map()方法中根据扫描到的当前module内所有路由注解,生成了调用Routers.map(…)方法来注册路由的代码
- 在Routers的所有api接口中最终都会触发RouterInit.init()方法,从而实现所有路由的映射表注册
这种方式用一个RouterInit类组合了所有module中的路由映射表类,运行时效率比扫描所有dex文件的方式要高,但需要额外在主工程代码中维护一个组件名称列表注解: @Modules({“app”, “sdk”})
我们最终选择的是利用AutoRegister的能力来实现自动注册。为了方便用户使用我自定义了一个Gradle plugin来实现此功能
class Plugin implements org.gradle.api.Plugin<Project> {
@Override
void apply(Project project) {
/**
* 注册transform接口
*/
def isApp = project.plugins.hasPlugin(AppPlugin)
if (isApp) {
println 'project(' + project.name + ') apply com.jst.component.initializer plugin'
def android = project.extensions.getByType(AppExtension)
def transformImpl = new RegisterTransform(project)
AutoRegisterConfig config = new AutoRegisterConfig()
Map<String, Object> map = new HashMap<>()
map.put('scanInterface','com.jst.compinit.IComponentInfo')
map.put('codeInsertToClassName','com.jst.compinit.ComponentInitializer')
map.put('registerMethodName','register')
config.registerInfo.add(map)
config.project = project
config.convertConfig()
transformImpl.config = config
android.registerTransform(transformImpl)
}
}
}
首先该依赖信息通过注解的方式来配置是最方便的。所以我们定义了一个@Component注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Component {
/**
* Component的name
*
* 一般情况下该name可以不设置
* 如果该Component被其他Component所依赖,则该name必须设置
*/
String name() default "";
/**
* 依赖的Component的name列表
*/
String[] dependencies(