了解组件化的架构
组件化,业务组件横向是不能有依赖,也就是需要解决组件通信的问题,所以我们要用到路由,业务组件通过路由转发功能和其他组件通信
应用AutoService Processor 写自己的路由
- Path 与 Group 的映射关系
- Path的生成管理 和 Group 的生成管理
- Path仓库 Group仓库 定义两个仓库存储,为了后续的管理工作 - - 准备工作: 路由对象封装,校验作用域为Activity,Fragment等需要的内容
- 校验路由对象 -> checkRoutePath(routeBean),赋值给仓库Path
- 生成一系列的Path
- 生成一系列的Group ---> 对应上Path
Group和Path 仓库其实就是内存缓存。通过Map保存
我们要借助JavaPoet 生成的Gropu和Path目标class
最终生成的效果:Group
public class ARouter$$Group$$personal implements ARouterGroup {
@Override
public Map<String, Class<? extends ARouterPath>> getGroupMap() {
Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
groupMap.put("personal", ARouter$$Path$$personal.class);
return groupMap;
}
}
最终生成的效果:Path
public class ARouter$$Path$$personal implements ARouterPath {
@Override
public Map<String, RouterBean> getPathMap() {
Map<String, RouterBean> pathMap = new HashMap<>();
pathMap.put("/personal/Personal_Main2Activity", RouterBean.create();
pathMap.put("/personal/Personal_MainActivity", RouterBean.create());
return pathMap;
}
}
核心arouter_compiler
我们需要继承AbstractProcccessor,并进行配置
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({ProcessorConfig.AROUTER_PACKAGE})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
// 注解处理器接收的参数
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.APT_PACKAGE})
public class ArouterProcessor extends AbstractProcessor
定义相关的成员,最终要的使我们的两个仓库缓存定义
// 操作Element的工具类(类,函数,属性,其实都是Element)
private Elements elementTool;
// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
private Types typeTool;
// Message用来打印 日志相关信息
private Messager messager;
// 文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
private Filer filer;
// 各个模块传递过来的模块名 例如:app order personal ---- 这个配合Gradle配置
private String options;
// 各个模块传递过来的目录 用于统一存放 apt生成的文件
private String aptPackage;
// 仓库1,Path 缓存 -> Map<"personal",Listt<RouteBean>>
private Map<String, List<RouterBean>> mAllPathMaps = new HashMap<>();
// 仓库2,Graoup 缓存 -> Map<"personal","ARouter$$personal.class">
private Map<String, String> mAllGroupMap = new HashMap<>();
有两个重要的函数init和process核心处理
初始化工作
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
filer = processingEnv.getFiler();
elementTool = processingEnv.getElementUtils();
messager = processingEnv.getMessager();
// 只有接受到 App壳 传递过来的数据,才能证明我们的 APT环境搭建完成
options = processingEnv.getOptions().get(ProcessorConfig.OPTIONS);
aptPackage = processingEnv.getOptions().get(ProcessorConfig.APT_PACKAGE);
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>init options:" + options);
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>init aptPackage:" + aptPackage);
if (options != null && aptPackage != null) {
messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境搭建完成....");
} else {
messager.printMessage(Diagnostic.Kind.NOTE, "APT 环境有问题,请检查 options 与 aptPackage 为null...");
}
}
核心process
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param annotations 使用了支持处理注解的节点集合
* @param roundEnv 当前或是之前的运行环境,可以通过该对象查找的注解。
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (annotations.isEmpty()) {
messager.printMessage(Diagnostic.Kind.NOTE, "未发现@Arouter注解的地方");
return false;
}
// 获取所有被@Arouter 注解的元素集合
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(ARouter.class);
// 通过Element工具类,获取Activity,Callback类型
TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGE);
// 显示类信息(获取被注解的节点,类节点)这也叫自描述 Mirror
TypeMirror activityMirror = activityType.asType();
// 遍历所有的类节点 包含注解的
for (Element element : elements) {
// 获取类节点,获取包节点
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
// 获取简单类名,例如:MainActivity
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>for 被@ARetuer注解的类有:" + className); // 打印出 就证明APT没有问题
messager.printMessage(Diagnostic.Kind.NOTE, ">>>>>>>>>>>>>>for Options:" + options); // 打印出 就证明APT没有问题
// 拿到注解
ARouter aRouter = element.getAnnotation(ARouter.class);
// TODO: 1.准备RouterBean 检查参数和路径
RouterBean routerBean = new RouterBean.Builder()
.addGroup(aRouter.group())
.addPath(aRouter.path())
.addElement(element)
.build();
...................检查路径
}
// TODO: 2. 第一步:生成系列PATH
...................PATH生成
// TODO: 3. 第二步:生成组Group
...................Group生成
elements.clear();
return true;
}
}
// 我们自己定义:ARouter注解的类 必须继承 Activity
// Main2Activity的具体详情 例如:继承了 Activity
TypeMirror elementMirror = element.asType();
// activityMirror android.app.Activity描述信息
if (typeTool.isSubtype(elementMirror, activityMirror)) {
routerBean.setTypeEnum(RouterBean.TypeEnum.ACTIVITY);
} else {
// 我们测试,随便写一个类DavidTest.java 加上@Arouter注解,但是不满足我们规则typeEnum
// 主动抛出异常
throw new RuntimeException("@Arouter 注解目前仅限用于Activity类上");
}
if (checkRouterPath(routerBean)) {
messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean Check Success:" + routerBean.toString());
// 赋值到 mAllPathMap 集合
List<RouterBean> routerBeans = mAllPathMaps.get(routerBean.getGroup());
// 如果从Map中找不到key为:bean.getGroup()的数据,就新建List集合再添加进Map
if(ProcessorUtils.isEmpty(routerBeans)){
routerBeans = new ArrayList<>();
routerBeans.add(routerBean);
mAllPathMaps.put(routerBean.getGroup(), routerBeans);
} else {
routerBeans.add(routerBean);
}
} else {
// ERROR 编译期发生异常
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按规范配置,如:/app/MainActivity");
}
检测Router 的path
/**
* 校验@ARouter注解的值,如果group未填写就从必填项path中截取数据
*
* @param routerBean 路由详细信息,最终实体封装类
*/
private boolean checkRouterPath(RouterBean routerBean) {
// "app" "order" "personal"
String group = routerBean.getGroup();
// "/app/MainActivity" "/order/Order_MainActivity" "/personal/Personal_MainActivity"
String path = routerBean.getPath();
// 校验 @ARouter注解中的path值,必须要以 / 开头(模仿阿里Arouter规范)
if(ProcessorUtils.isEmpty(path) || !path.startsWith("/")){//TextUtils Java 工程无法引用,所以封装processutils
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
}
// 因为用户使用 @ARouter(path="/app/MainActivity" group="app") 我们默认省略group,替业务程序取出组名app
String finalGroup = path.substring(1, path.indexOf("/",1));
// 检查 @ARouter注解中group赋值情况
if(!ProcessorUtils.isEmpty(group) && !group.equals(options)){
// 架构师定义规范,让开发者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必须和子模块名一致!");
return false;
} else {
routerBean.setGroup(finalGroup);
}
return true;
}
// TODO: 2. 第一步:生成系列PATH
try {
createPathFile(pathType); // 生成 Path类
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "在生成PATH模板时,异常了 e:" + e.getMessage());
}
--------------------
/**
* 系列Path的类 生成工作
*
* @param pathType ARouterPath 高层的标准
* @throws IOException
*/
private void createPathFile(TypeElement pathType) throws IOException {
if (ProcessorUtils.isEmpty(mAllPathMaps)) {
return;
}
TypeName methodReturn = ParameterizedTypeName.get(
ClassName.get(Map.class),//Map
ClassName.get(String.class),//Map<String,
ClassName.get(RouterBean.class)//Map<String,RouterBean>
);
// 遍历仓库 app,order,personal
for (Map.Entry<String, List<RouterBean>> entry : mAllPathMaps.entrySet()) {
// 1. 方法
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.PATH_METHOD_NAME)
.addAnnotation(Override.class)// 给方法上添加注解 @Override
.addModifiers(Modifier.PUBLIC)// public修饰符
.returns(methodReturn);// 把Map<String, RouterBean> 加入方法返回
// Map<String, RouterBean> pathMap = new HashMap<>(); 如果是$T class类型一般要包装一下
methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),// Map
ClassName.get(String.class),//Map<String,
ClassName.get(RouterBean.class),//Map<String, RouterBean>
ProcessorConfig.PATH_VAR1,//Map<String, RouterBean> pathMap
ClassName.get(HashMap.class)//Map<String, RouterBean> pathMap = new HashMap<>();
);
// 循环添加map赋值
List<RouterBean> pathList = entry.getValue();
// pathMap.put("/app/Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY, Main2Activity.class, "/app/Main2Activity", "app"));
for (RouterBean bean : pathList) {
methodBuilder.addStatement("$N.put($S, $T.create($T.$L,$T.class,$S,$S))",
ProcessorConfig.PATH_VAR1,
bean.getPath(),
ClassName.get(RouterBean.class),
ClassName.get(RouterBean.TypeEnum.class),
bean.getTypeEnum(),
ClassName.get((TypeElement) bean.getElement()),
bean.getPath(),
bean.getGroup()
);
}// end for
// return pathMap;
methodBuilder.addStatement("return $N", ProcessorConfig.PATH_VAR1);
// TODO 注意:不能像以前一样,1.方法,2.类 3.包, 因为这里面有implements ,所以 方法和类要合为一体生成才行,这是特殊情况
String finalClassName = ProcessorConfig.PATH_FILE_NAME + entry.getKey();
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
aptPackage + "." + finalClassName);
// 生成类文件:public class ARouter$$Path$$personal implements ARouterPath
JavaFile.builder(aptPackage,
TypeSpec.classBuilder(finalClassName)
.addSuperinterface(ClassName.get(pathType))// 实现ARouterLoadPath接口 implements ARouterPath==pathType
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuilder.build())// 方法的构建(方法参数 + 方法体)
.build())// 类构建完成
.build()// JavaFile构建完成
.writeTo(filer);// 文件生成器开始生成类文件
// 仓库二 缓存二 非常重要一步,注意:PATH 路径文件生成出来了,才能赋值路由组mAllGroupMap
mAllGroupMap.put(entry.getKey(), finalClassName);
}
}
try {
createGroupFile(groupType, pathType);
} catch (IOException e) {
e.printStackTrace();
messager.printMessage(Diagnostic.Kind.NOTE, "在生成GROUP模板时,异常了 e:" + e.getMessage());
}
------------
/**
* 生成路由组Group文件,如:ARouter$$Group$$app
*
* @param groupType ARouterLoadGroup接口信息
* @param pathType ARouterLoadPath接口信息
*/
private void createGroupFile(TypeElement groupType, TypeElement pathType) throws IOException {
if (ProcessorUtils.isEmpty(mAllGroupMap) || ProcessorUtils.isEmpty(mAllPathMaps)) {
return;
}
TypeName methodReturn = ParameterizedTypeName.get(
ClassName.get(Map.class),//Map
ClassName.get(String.class),//Map<String,
// Class<? extends ARouterPath>> 难度
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathType)))//? extends ARouterPath
// WildcardTypeName.supertypeOf() 做实验 ? super
);
// 1.方法 public Map<String, Class<? extends ARouterPath>> getGroupMap() {
MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(ProcessorConfig.GROUP_METHOD_NAME) // 方法名
.addAnnotation(Override.class) // 重写注解 @Override
.addModifiers(Modifier.PUBLIC) // public修饰符
.returns(methodReturn); // 方法返回值
//Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathType))),
ProcessorConfig.GROUP_VAR1,
ClassName.get(HashMap.class)
);
// groupMap.put("personal", ARouter$$Path$$personal.class);
// groupMap.put("order", ARouter$$Path$$order.class);
for (Map.Entry<String, String> entry : mAllGroupMap.entrySet()) {
methodBuidler.addStatement("$N.put($S, $T.class)",
ProcessorConfig.GROUP_VAR1, // groupMap.put
entry.getKey(), // order, personal ,app
ClassName.get(aptPackage, entry.getValue()));
}
// return groupMap;
methodBuidler.addStatement("return $N", ProcessorConfig.GROUP_VAR1);
// 最终生成的类文件名 ARouter$$Group$$ + personal
String finalClassName = ProcessorConfig.GROUP_FILE_NAME + options;
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
aptPackage + "." + finalClassName);
// 生成文件: ARouter$$Group$$persional
JavaFile.builder(aptPackage,
TypeSpec.classBuilder(finalClassName)
.addSuperinterface(ClassName.get(pathType))// 实现ARouterLoadPath接口 implements ARouterPath==pathType
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuidler.build())// 方法的构建(方法参数 + 方法体)
.build())// 类构建完成
.build()// JavaFile构建完成
.writeTo(filer);// 文件生成器开始生成类文件
}
------------------------补充----------------------------
RouteBean 的封装
/**
* 最终路由 要 传递 对象
* <p>
* 路由路径Path的最终实体封装类
* 例如:app分组中的MainActivity对象,这个对象有更多的属性
*/
public class RouterBean {
// 为了以后的扩展
public enum TypeEnum {
ACTIVITY
}
private TypeEnum typeEnum;// 枚举类型:Activity
private Element element;// 类节点 JavaPoet学习的时候,可以拿到很多的信息
private Class<?> myclass;// 被注解的 Class对象 例如: MainActivity.class Main2Activity.class Main3Activity.class
private String path;// 路由地址 例如:/app/MainActivity
private String group; // 路由组 例如:app order personal
public RouterBean(TypeEnum typeEnum, /*Element element,*/ Class<?> myclass, String path, String group) {
this.typeEnum = typeEnum;
// this.element = element;
this.myclass = myclass;
this.path = path;
this.group = group;
}
// 对外暴露
// 对外提供简易版构造方法,主要是为了方便APT生成代码
public static RouterBean create(TypeEnum type, Class<?> clazz, String path, String group) {
return new RouterBean(type, clazz, path, group);
}
public RouterBean(Builder builder) {
this.typeEnum = builder.type;
// this.element = builder.element;
this.myclass = builder.clazz;
this.path = builder.path;
this.group = builder.group;
}
public TypeEnum getTypeEnum() {
return typeEnum;
}
public Element getElement() {
return element;
}
public Class<?> getMyclass() {
return myclass;
}
public String getPath() {
return path;
}
public String getGroup() {
return group;
}
public static class Builder {
// 枚举类型:Activity
private TypeEnum type;
// 类节点
private Element element;
// 注解使用的类对象
private Class<?> clazz;
// 路由地址
private String path;
// 路由组
private String group;
public Builder addType(TypeEnum type) {
this.type = type;
return this;
}
public Builder addElement(Element element) {
this.element = element;
return this;
}
public Builder addClazz(Class<?> clazz) {
this.clazz = clazz;
return this;
}
public Builder addPath(String path) {
this.path = path;
return this;
}
public Builder addGroup(String group) {
this.group = group;
return this;
}
public RouterBean build() {
if(path == null || path.length() == 0){
throw new IllegalArgumentException("path必填项为空,应该为如:/app/MainActivity");
}
return new RouterBean(this);
}
}
知识点
Diagnostic.Kind.ERROR
还有这些 日志级别打印:WARNING,MANDATORY_WARNING,NOTE,OTHER
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必须要以 / 开头");
Map<String, RouterBean> getPathMap()
生成
TypeName非常重要,在使用javapoet时经常需要它。它可以标识任何声明的类。声明的类型只是Java富类型系统的开始:我们还有数组、参数化类型、通配符类型和类型变量
TypeName methodReturn = ParameterizedTypeName.get(
ClassName.get(Map.class),//Map
ClassName.get(String.class),//Map<String,
ClassName.get(RouterBean.class)//Map<String,RouterBean>
);
占位 $T $S $N $L
$T 类class
$N 变量
$L 在枚举代码中,我们使用TypeSpec.anonymousInnerClass(). 匿名内部类也可以用在代码块中。它们是可以用$L引用的值
$S 字符串
// Map<String, RouterBean> pathMap = new HashMap<>(); 如果是$T class类型一般要包装一下
methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),// Map
ClassName.get(String.class),//Map<String,
ClassName.get(RouterBean.class),//Map<String, RouterBean>
ProcessorConfig.PATH_VAR1,//Map<String, RouterBean> pathMap
ClassName.get(HashMap.class)//Map<String, RouterBean> pathMap = new HashMap<>();
);
JavaPoet 语法挺绕口的,可以参见github官网文档