- 组件之间只能通过接口和 Uri 进行通讯(隔离代码)
- 各个组件均可独立运行(可以执行 Assemble 任务)
- 校验 + 冲突检测(接口实现、Uri 实现)
首先,各个组件都依赖 ComponentService 模块,该模块包含所有 Service 接口以及基于注解处理器生成的 Uri 信息
sourceSets {
main {
java.srcDirs = [
'src/main/java',
project.getRootDir().getAbsolutePath() + '/RouteTable/java'
]
}
}
我们通过 isRunAlone 来设置组件是否可以独立运行,通过 debugComponent 和 releaseComponent 设置组件之间的依赖关系
//各个子工程gradle.properties:
isRunAlone=true
debugComponent=onecomponent,secondcomponent,thirdcomponent
compileComponent=onecomponent
//各个子工程build.gradle:
apply plugin: 'com.lede.component'
dependencies {
api project(':componentservice')
annotationProcessor project(':component-annotation')
}
//根工程gradle.properties:
mainmodulename=app
我们在 Gradle 插件、Android Transform、AnnotationProcessor 中处理具体逻辑。首先我们获取当前执行的 Task 信息,判断 Task 类型(Assemble、Release、Debug),并通过分割 taskNames 获取当前要执行的插件。如果当前是 Assemble 任务,我们需要动态将其他插件的 isRunAlone 修改为 false
//taskNames: project.gradle.startParameter.taskNames
private static AssembleTask getTaskInfo(List<String> taskNames) {
AssembleTask assembleTask = new AssembleTask()
for (String task : taskNames) {
if (task.toUpperCase().contains("ASSEMBLE") || task.contains("aR")) {
if (task.toUpperCase().contains("DEBUG")) {
assembleTask.isDebug = true
}
assembleTask.isAssemble = true
String[] strs = task.split(":")
assembleTask.modules.add(strs.length > 1 ? strs[strs.length - 2] : "")
break
}
}
return assembleTask
}
private void fetchMainmodulename(Project project, AssembleTask assembleTask) {
if (!project.rootProject.hasProperty("mainmodulename")) {
throw new RuntimeException(
"you should set mainmodulename in root project's gradle.properties")
}
if (assembleTask.modules.size() > 0 && assembleTask.modules.get(0) != null
&& assembleTask.modules.get(0).trim().length() > 0
&& !assembleTask.modules.get(0).equals("")) {
compileModule = assembleTask.modules.get(0)
} else {
compileModule = project.rootProject.property("mainmodulename")
}
if (compileModule == null || compileModule.trim().length() <= 0) {
throw new RuntimeException(
"you should set mainmodulename in root project's gradle.properties")
}
}
//对于isRunAlone==true的情况需要根据实际情况修改其值
boolean isRunAlone = Boolean.parseBoolean((project.properties.get("isRunAlone")))
String mainmodulename = project.rootProject.property("mainmodulename")
if (isRunAlone && assembleTask.isAssemble) {
//对于要编译的组件和主项目,isRunAlone修改为true,其他组件都强制修改为false
//这就意味着组件不能引用主项目,这在层级结构里面也是这么规定的
if (module.equals(compileModule) || module.equals(mainmodulename)) {
isRunAlone = true
} else {
isRunAlone = false
}
}
对于 RunAlone 模式的插件,为其添加 application 插件,添加 RunAlone 资源目录,添加组件依赖,并注册 Transform Api。对于非 RunAlone 模式的插件,为其添加 library 插件
if (isRunAlone) {
project.apply plugin: 'com.android.application'
if (!module.equals(mainmodulename)) {
project.android.sourceSets {
main {
manifest.srcFile 'src/main/runalone/AndroidManifest.xml'
java.srcDirs = ['src/main/java', 'src/main/runalone/java']
res.srcDirs = ['src/main/res', 'src/main/runalone/res']
jniLibs.srcDirs = ['libs', 'src/main/jniLibs', 'src/main/runalone/jniLibs']
}
}
}
if (assembleTask.isAssemble && module.equals(compileModule)) {
compileComponents(assembleTask, project)
project.android.registerTransform(new ComCodeTransform(project))
}
} else {
project.apply plugin: 'com.android.library'
}
private void compileComponents(AssembleTask assembleTask, Project project) {
String components
if (assembleTask.isDebug) {
components = (String) project.properties.get("debugComponent")
} else {
components = (String) project.properties.get("compileComponent")
}
String[] compileComponents = components.split(",")
for (String str : compileComponents) {
Project targetProject = getTargetProject(project, str.trim())
if (targetProject != null) {
project.dependencies.add("implementation", targetProject)
}
}
}
private Project getTargetProject(Project project, String keywords) {
Project targetProject = null
project.rootProject.subprojects.each {
String[] arrays = it.name.split(":")
if (arrays[arrays.length - 1].toUpperCase().equals(keywords.toUpperCase())) {
targetProject = it
}
}
return targetProject
}
所有组件都要实现 IApplicationLike 接口,另外 RunaloneApplicaiton 也实现了 IApplicationLike 接口。在 Transform 中,我们收集实现 IApplicationLike 接口的类(排除继承 RunaloneApplicaiton 的类),并记录到 ComponentCollectors 中
private static boolean isActivator(CtClass ctClass) {
for (CtClass ctClassInter : ctClass.getInterfaces()) {
if (ctClassInter.name.endsWith("IApplicationLike")) return true
}
return false
}
//从activators中去掉application
Iterator<CtClass> iterator = activators.iterator()
while (iterator.hasNext()) {
CtClass ctClass = iterator.next()
if (!"java.lang.Object".equals(ctClass.getSuperclass().name)) {
iterator.remove()
}
}
/*
* this is auto generate file
* TradeApplike=com.lede.netease.trade.applike.TradeApplike
* LoginApplike=com.lede.netease.login.applike.LoginApplike
* ChartApplike=com.lede.netease.chart.applike.ChartApplike
*/
public class TradeApplike implements IApplicationLike {
@Override
public void initComponent(Context context) {
}
@Override
public String getComponentName() {
return "trade";
}
}
组件之间通过 Router 类以及接口和短链进行通讯:
Router.getInstance().getService(TradeService.class);
Router.getInstance().openUri(getActivity(),"ntesfa://login/LoginActivity",null);
在 RunaloneApplicaiton 的 onCreate 方法中反射获取 ComponentCollectors 类,读取并通过反射实例化各个组件的入口类,调用 initComponent 方法,然后通过 getComponentName 方法获取组件名,进而反射实例化(组件名$$ComponentRouter)类,实例保存在 Router 中用来实现路由跳转
public class RunaloneApplication extends IApplicationLike {
public void initComponent(Context context) {
SequencedProperties prop = getComponentProperties();
for (Object key : prop.getKeyList()) {
Class clazz = Class.forName(prop.getProperty(key.toString()));
IApplicationLike appLike = (IApplicationLike) clazz.newInstance();
Router.getInstance().registerRouter(appLike.getComponentName());
mIApplicationLikes.add(appLike);
}
for(IApplicationLike applike : mIApplicationLikes){
applike.initComponent(context);
}
}
public SequencedProperties getComponentProperties() {
InputStream in = ComponentConfig.class.getResourceAsStream("/assets/component.properties");
SequencedProperties prop = new SequencedProperties();
prop.load(in);
return prop;
}
}
public class Router implements IRouter {
@Override
public void registerRouter(String componentName) {
IComponentRouter router = getComponentRouter(componentName);
if (router != null) {
registerRouter(router);
}
}
public void registerRouter(IComponentRouter router) {
if (componentRouters.contains(router)){
componentRouters.remove(router);
}
componentRouters.add(router);
}
private IComponentRouter getComponentRouter(String componentName) {
String path = genComponentRouterClass(componentName);
if (routerInstanceCache.containsKey(path)) {
return routerInstanceCache.get(path);
}
Class cla = Class.forName(path);
IComponentRouter instance = (IComponentRouter) cla.newInstance();
routerInstanceCache.put(path, instance);
return instance;
}
public static String genComponentRouterClass(String componentName) {
return "com.lede.gen.router" + "." + firstCharUpperCase(componentName) + "$$ComponentRouter";
}
}
在路由跳转的时候,Router 会遍历所有的 ComponentRouter,如果 Component 没有初始化,则先执行 initMap 方法
public class Login$$ComponentRouter extends ComponentRouter {
@Override
public String getComponentName() {
return "login";
}
@Override
public void initMap() {
super.initMap();
routeMapper.put("/login/thirdActivity",ThirdActivity.class);
routeMapper.put("/login/secondActivity",SecondActivity.class);
serviceMapper.put(SecondService.class,SecondServiceImpl.class);
}
}
public class Router implements IRouter {
// ntesfa://secondcomponent/secondActivity
@Override
public boolean openUri(Context context, String url, Bundle bundle) {
Uri uri = Uri.parse(url);
return openUri(context, uri, bundle);
}
@Override
public boolean openUri(Context context, Uri uri, Bundle bundle) {
for (IComponentRouter temp : componentRouters) {
if(temp.verifyUri(uri)){
temp.openUri(context, uri, bundle);
}
}
return true;
}
}
public abstract class ComponentRouter implements IComponentRouter {
@Override
public boolean verifyUri(Uri uri) {
if (uri == null || uri.getScheme() == null) {
return false;
}
String host = uri.getHost();
if (!getComponentName().equals(host)) {
return false;
}
if (!hasInitMap) {
initMap();
}
return true;
}
}
对于接口实现类,需要添加 @ServiceNode 注解,对于 Uri 实现类,需要添加 @RouteNode(path = "/login/thirdActivity") 注解,框架对应生成(组件名$$ComponentRouter)类
具体原理:javapoet、Annotation Processing、AutoService
Annotation Processor 是 Javac 的一个工具,它用来在编译时扫描和处理注解,自定义注解处理器需要继承 AbstractProcessor 类,并添加 @AutoService 注解,该注解可以自动生成 META-INF/services/javax.annotation.processing.Processor 文件,AutoService 是 Google 开发的一个库,使用时需要添加依赖
AbstractProcessor 是一个抽象类,有四个核心方法:init、process、getSupportedSourceVersion、getSupportedAnnotationTypes
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Filer mFiler = processingEnv.getFiler();
Types mTypeUtils = processingEnv.getTypeUtils();
Messager mMessager = processingEnv.getMessager();
Elements mElementUtils = processingEnv.getElementUtils();
Map<String, String> options = processingEnv.getOptions();
componentName = options.get("moduleName");
}
TypeElement
代表源代码中的元素类型(包、类、方法、变量),但是TypeElement
并不包含类的相关信息,你可以从TypeElement
获取类的名称,但你不能获取类的信息,比如说父类,这些信息可以通过TypeMirror
获取。你可以通过调用element.asType()
来获取TypeElement
的TypeMirror
//RouterProcessor.java
String ACTIVITY = "android.app.Activity";
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> routeNodes = roundEnv.getElementsAnnotatedWith(RouteNode.class);
if(routeNodes.size() > 0) {
TypeMirror type_Activity = mElementUtils.getTypeElement(ACTIVITY).asType();
for(Element element : routeNodes) {
TypeMirror tm = element.asType();
RouteNode routeNode = element.getAnnotation(RouteNode.class);
if (processingEnv.getTypeUtils().isSubtype(tm, type_Activity)){
Node node = new Node();
node.setPath(routeNode.path());
node.setDesc(routeNode.desc());
node.setRawType(element);
node.setNodeType(NodeType.TYPE_ACTIVITY); //NodeType.TYPE_SERVICE
if (!nodesList.contains(node)) {
nodesList.add(node);
}
}
}
generateComponentRouter();//生成组件componentRouter
generateRouterTableJava();//生成组件路由表
}
}
String COMPONENT_ROUTER = "com.lede.component.componentlib.router.ComponentRouter";
private void generateComponentRouter() {
//com.lede.gen.router.ThirdComponentComponentRouter
String claName = RouteUtils.genComponentRouterClass(componentName);
//pkg
String pkg = claName.substring(0, claName.lastIndexOf("."));
//simpleName
String cn = claName.substring(claName.lastIndexOf(".") + 1);
//superClassName
TypeElement typeBase = processingEnv.getElementUtils().getTypeElement(COMPONENT_ROUTER);
/********* JavaPoet *********/
ClassName superClass = ClassName.get(typeBase);
MethodSpec getComponentNameMethod = generateGetComponentNameMethod();
MethodSpec initMapMethod = generateInitMapMethod();
JavaFile.builder(pkg, TypeSpec.classBuilder(cn)
.addModifiers(PUBLIC)
.superclass(superClass)
.addMethod(getComponentNameMethod)
.addMethod(initMapMethod)
.build()
).build().writeTo(mFiler);
}
private MethodSpec generateGetComponentNameMethod() {
TypeName returnType = TypeName.get(processingEnv.getElementUtils()
.getTypeElement("java.lang.String").asType());
MethodSpec.Builder getComponentNameMethodSpecBuilder = MethodSpec.methodBuilder("getComponentName")
.returns(returnType)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC);
getComponentNameMethodSpecBuilder.addStatement("return $S",componentName);
return getComponentNameMethodSpecBuilder.build();
}
//$S for Strings
//$T for Types
private MethodSpec generateInitMapMethod() {
TypeName returnType = TypeName.VOID;
MethodSpec.Builder initMapMethodSpecBuilder =
MethodSpec.methodBuilder("initMap")
.returns(returnType)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC);
initMapMethodSpecBuilder.addStatement("super.initMap()");
for (Node node : nodesList) {
if(node.getNodeType() == NodeType.TYPE_ACTIVITY) {
initMapMethodSpecBuilder.addStatement(
"routeMapper" + ".put($S,$T.class)",
node.getPath(),
ClassName.get((TypeElement) node.getRawType()));
}
}
return initMapMethodSpecBuilder.build();
}
//InjectProcessor.java
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
categories(roundEnv.getElementsAnnotatedWith(Inject.class));
generateHelper();
return true;
}
private void categories(Set<? extends Element> elements) {
for (Element element : elements) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
if (element.getModifiers().contains(Modifier.PRIVATE)) {
throw new IllegalAccessException("The injected fields CAN NOT BE private");
}
if (parentAndChild.containsKey(enclosingElement)) {
parentAndChild.get(enclosingElement).add(element);
} else {
List<Element> children = new ArrayList<>();
children.add(element);
parentAndChild.put(enclosingElement, children);
}
}
}
TypeElement type_Injector =
mElementUtils.getTypeElement("com.lede.component.componentlib.inject.Injector");
TypeElement type_JsonService =
mElementUtils.getTypeElement("com.lede.component.componentlib.service.JsonService");
TypeElement parent = entry.getKey();
String fileName = parent.getSimpleName() + "$$Router$$InjectHelper";
TypeSpec.Builder helper = TypeSpec.classBuilder(fileName)
.addJavadoc("Auto generated by " + TAG)
.addSuperinterface(ClassName.get(type_Injector))
.addModifiers(PUBLIC);
FieldSpec jsonServiceField =
FieldSpec.builder(
TypeName.get(type_JsonService.asType()),
"jsonService",
Modifier.PRIVATE).build();
helper.addField(jsonServiceField);
ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, "target").build();
MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject")
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(objectParamSpec);
injectMethodBuilder.addStatement(
"jsonService = $T.Factory.getSingletonImpl()",
ClassName.get(type_JsonService));
injectMethodBuilder.addStatement(
"$T substitute = ($T)target",
ClassName.get(parent),
ClassName.get(parent));
injectMethodBuilder.beginControlFlow("if (null != jsonService)");
injectMethodBuilder.addStatement(
"substitute." + fieldName + " = " + statement,
(StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name()),
ClassName.get(element.asType()));
injectMethodBuilder.nextControlFlow("else");
injectMethodBuilder.addStatement(
"$T.e(\"JsonService not found in Router\")", AndroidLog);
injectMethodBuilder.endControlFlow();
helper.addMethod(injectMethodBuilder.build());
JavaPoet简介:
- $L:数值,addStatement("int value=$L", 10)
- $S:字符串,如果被这个用这个占位符占位的会被2个双引号包裹住
- $T:类型,这个会自动导包addStatement("$T obj=new $T()", Persion.class, Persion.class) 生成的就是new Persion(),接收一个class字节码
- $N:引用,比如你有一个MethodSpec对象,如果你想调用这个方法
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
.addStatement("result[1] = $N(b & 0xf)", hexDigit)
.addStatement("return new String(result)")
.build();
public String byteToHex(int b) {
char[] result = new char[2];
result[0] = hexDigit((b >>> 4) & 0xf);
result[1] = hexDigit(b & 0xf);
return new String(result);
}
public char hexDigit(int i) {
return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
MethodSpec出了可以用addStatement也可以使用addCode
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();