APT(Annotation Processing Tool)注解处理器,是一种处理注解的工具。
APT 在编译时期扫描源代码中的注解,开发中根据注解,利用 APT 自动生成 Java 代码,减少冗余代码和手动的代码输入,提高编码效率。
在复写 AbstractProcessor 的 init 方法时,参数就是一个 ProcessingEnvironment 对象。它内部提供一下实用对象,我们可以利用这些对象,来实现需要的功能。
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
...
}
在 APT 阶段,任何事物都被成为元素。比如一个对象、一个类、一个方法、一个参数,它们都被统一称为元素。 Element 是个接口类,有很多子类。子类在其基础上增加了额外的接口方法来描述具体事物的特殊属性。
TypeElement: 一个类或接口程序元素
VariableElement: 一个字段、 enum 常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement: 某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
PackageElement: 一个包程序元素
TypeParameterElement: 一般类、接口、方法或构造方法元素的泛型参数
是一个枚举类,用于判断元素;
包含 PACKAGE(包),CLASS(类),INTERFACE(接口),FIELD(变量),PARAMETER(参数),METHOD(方法)
处理 Element 对象的工具类
Filer 是一个文件操作的接口,可以创建或写入一个 Java 文件。和一般的文件区别在于它是专门处理 Java 文件,以 .java 或 .class 为后缀的文件。在 APT 中,在自动代码生成后,用 Filer 生成一个 .java 或 .class 文件。
是一个操作类型的工具类,也可以操作 TypeMirror 对象。
例如如果想知道变量的类型,可以通过 Types 相关类处理。
// 判断是否为 Parcelable 类型
String PARCELABLE = "android.os.Parcelable"
TypeMirror parcelableType = elements.getTypeElement(PARCELABLE).asType();
if (types.isSubtype(typeMirror, parcelableType)) {
// PARCELABLE
return TypeKind.PARCELABLE.ordinal();
}
表示数据类型,包含基本类型 int、 boolean,也包含复杂类型,例如 自定义类、数组、Parcelable 等
修饰词。有 PUBLIC, PROTECTED, PRIVATE, ABSTRACT, DEFAULT等
复写 AbstractProcessor 的 process 方法,其中一个参数就是 RoundEnvironment。 可以通过 RoundEnvironment 对象获取在代码中设置的相关注解的 Element
开源库 ARouter 是一个广泛应用的路由,它也是使用 APT 方式,生成相应的代码。
在使用 APT 生成代码使用了 JavaPoet , JavaPoet 使用比较简单,根据官方文档直接去操作即可。至于如何使用 JavaPoet ,请参考我之前的文章 JavaPoet 和 Java 注解在 Android 中的应用.
下面是以 ARouter 的 RouteProcessor 为例,说明代码生成的过程
RouteProcessor 使用生成类似下面这样的一个 .java 文件,用于路由的调整,同时传递参数
package com.alibaba.android.arouter.routes;
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
}
}
我们看到上面生成的文件,想想是怎样生成的
在 RouteProcessor#parseRoutes 方法中
/*
* 生成 ```Map```
*/
ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class)
);
/*
* Build input param name.
* 生成参数 (Map atlas)
*/
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build();
/**
* 生成 @Override
* public void loadInto(Map atlas)
*/
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
方法生成之后,就需要填充里面 map 内容了
// Make map body for paramsType
StringBuilder mapBodyBuilder = new StringBuilder();
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");
// put("key1", 8);
}
}
String mapBody = mapBodyBuilder.toString();
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
routeMeta.getPath(), // "/test/activity2"
routeMetaCn, // RouteMeta
routeTypeCn, // RouteMeta
className, // Test2Activity
routeMeta.getPath().toLowerCase(), //"/test/activity2"
routeMeta.getGroup().toLowerCase()); // test
现在是所有内容都生成了,接下来就是要生成相应的 .java 文件了
// Generate groups
String groupFileName = NAME_OF_GROUP + groupName; // ARouter$$Group
JavaFile.builder(PACKAGE_OF_GENERATE_FILE, //package com.alibaba.android.arouter.routes
TypeSpec.classBuilder(groupFileName)
.addJavadoc(WARNING_TIPS) // DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER.
.addSuperinterface(ClassName.get(type_IRouteGroup)) // 实现接口
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(mFiler);
APT 可以生产很多结构性的代码,可以节约我们日常开发的时间,它的使用也比较简单。