APT-概念了解
友情链接:
https://lizhaoxuan.github.io/2016/07/17/apt-Grammar-explanation/
https://github.com/Gavin-ZYX/APTTest.git
apt
APT(Annotation Processing Tool)是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注释自动生成代码。Annotation处理器在出来Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
annotationProcessor
annotationProcessor是APT工具中的一种,他是google开发的内置框架,不需要引入,可以直接在build.gradle文件中使用
android-apt
android-apt是由一位开发者自己开发的apt框架,源代码托管在这里,随着Android Gradle 插件 2.2 版本的发布,Android Gradle 插件提供了名为 annotationProcessor 的功能来完全代替 android-apt ,自此android-apt 作者在官网发表声明最新的Android Gradle插件现在已经支持annotationProcessor,并警告和或阻止android-apt ,并推荐大家使用 Android 官方插件annotationProcessor。
Demo-知识点
注解
@Retention
- @Retention(RetentionPolicy.SOURCE) 源码时注解,一般用来作为编译器标记。就比如Override, Deprecated, SuppressWarnings这样的注解
- @Retention(RetentionPolicy.RUNTIME) 运行时注解,一般在运行时通过反射去识别的注解
- @Retention(RetentionPolicy.CLASS) 编译时注解,在编译时处理
@Target
- @Target(ElementType.TYPE) 接口、类、枚举、注解
- @Target(ElementType.FIELD)字段、枚举的常量
- @Target(ElementType.METHOD) 方法
- @Target(ElementType.PARAMETER) 方法参数
- @Target(ElementType.CONSTRUCTOR) 构造函数
- @Target(ElementType.LOCAL_VARIABLE) 局部变量
- @Target(ElementType.ANNOTATION_TYPE) 注解
- @Target(ElementType.package) 包
@Inherited
该注解的字面意识是继承,但你要知道注解是不可以继承的。
ie:当你的注解定义到类A上,此时,有个B类继承A,且没使用该注解。但是扫描的时候,会把A类设置的注解,扫描到B类上
输出Log
Messager
//取得Messager对象
Messager messager = processingEnv.getMessager();
Processor日志输出的位置在编译器下方的Messages窗口中
Processor支持最基础的System.out方法
同样Processor也有自己的Log输出工具: Messager
同Log类似,Messager也有日志级别的选择
- Diagnostic.Kind.ERROR
- Diagnostic.Kind.WARNING
- Diagnostic.Kind.MANDATORY_WARNING
- Diagnostic.Kind.NOTE
- Diagnostic.Kind.OTHER
Element
Represents a program element such as a package, class, or method.
Each element represents a static, language-level construct (and not, for example, a runtime construct of the virtual machine).
表示一个程序元素,比如包、类或者方法
ExecutableElement
表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
对应@Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)
PackageElement;
表示一个包程序元素。提供对有关包极其成员的信息访问。
对应@Target(ElementType.PACKAGE)
TypeElement;
表示一个类或接口程序元素。提供对有关类型极其成员的信息访问。
对应@Target(ElementType.TYPE)
注意:枚举类型是一种类,而注解类型是一种接口。
TypeParameterElement;
表示一般类、接口、方法或构造方法元素的类型参数。
对应@Target(ElementType.PARAMETER)
VariableElement;
表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数。
对应@Target(ElementType.LOCAL_VARIABLE)
修饰方法的注解和ExecutableElement
当你有一个注解是以@Target(ElementType.METHOD)定义时,表示该注解只能修饰方法。
那么这个时候你为了生成代码,而需要获取一些基本信息:包名、类名、方法名、参数类型、返回值。
//OnceClick.class 以 @Target(ElementType.METHOD)修饰
for (Element element : roundEnv.getElementsAnnotatedWith(OnceClick.class)) {
//对于Element直接强转
ExecutableElement executableElement = (ExecutableElement) element;
//非对应的Element,通过getEnclosingElement转换获取
TypeElement classElement = (TypeElement) element
.getEnclosingElement();
//当(ExecutableElement) element成立时,使用(PackageElement) element
// .getEnclosingElement();将报错。
//需要使用elementUtils来获取
Elements elementUtils = processingEnv.getElementUtils();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//全类名
String fullClassName = classElement.getQualifiedName().toString();
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//方法名
String methodName = executableElement.getSimpleName().toString();
//取得方法参数列表
List extends VariableElement> methodParameters = executableElement.getParameters();
//参数类型列表
List types = new ArrayList<>();
for (VariableElement variableElement : methodParameters) {
TypeMirror methodParameterType = variableElement.asType();
if (methodParameterType instanceof TypeVariable) {
TypeVariable typeVariable = (TypeVariable) methodParameterType;
methodParameterType = typeVariable.getUpperBound();
}
//参数名
String parameterName = variableElement.getSimpleName().toString();
//参数类型
String parameteKind = methodParameterType.toString();
types.add(methodParameterType.toString());
}
}
修饰属性、类成员的注解和VariableElement
当你有一个注解是以@Target(ElementType.FIELD)定义时,表示该注解只能修饰属性、类成员。
那么这个时候你为了生成代码,而需要获取一些基本信息:包名、类名、类成员类型、类成员名
如何获取:
for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
//ElementType.FIELD注解可以直接强转VariableElement
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) element
.getEnclosingElement();
PackageElement packageElement = elementUtils.getPackageOf(classElement);
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//类成员名
String variableName = variableElement.getSimpleName().toString();
//类成员类型
TypeMirror typeMirror = variableElement.asType();
String type = typeMirror.toString();
}
修饰类的注解和TypeElement
当你有一个注解是以@Target(ElementType.TYPE)定义时,表示该注解只能修饰类、接口、枚举。
那么这个时候你为了生成代码,而需要获取一些基本信息:包名、类名、全类名、父类。
如何获取:
for (Element element : roundEnv.getElementsAnnotatedWith(xxx.class)) {
//ElementType.TYPE注解可以直接强转TypeElement
TypeElement classElement = (TypeElement) element;
PackageElement packageElement = (PackageElement) element
.getEnclosingElement();
//全类名
String fullClassName = classElement.getQualifiedName().toString();
//类名
String className = classElement.getSimpleName().toString();
//包名
String packageName = packageElement.getQualifiedName().toString();
//父类名
String superClassName = classElement.getSuperclass().toString();
}
Demo
module
apt-annotation (java-library)
apt-processor (java-library)
apt-library (com.android.library)
apt-annotation
注解类BindView(编译时注解)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
apt-processor
build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt-annotation')
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
AbstractProcessor
@SupportedAnnotationTypes({"com.example.gavin.apt_annotation.BindView"})
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
添加自己需要处理的注解,可以通过两种方式:getSupportedAnnotationTypes()或者直接用注解
@SupportedAnnotationTypes("全路径")
process方法
mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
// 1.会执行多次,所以要先clear
mProxyMap.clear();
// 2.得到所有的注解并收集到map中
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
VariableElement variableElement = (VariableElement) element;
TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
String fullClassName = classElement.getQualifiedName().toString();
//elements的信息保存到mProxyMap中
ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
if (proxy == null) {
proxy = new ClassCreatorProxy(mElementUtils, classElement);
mProxyMap.put(fullClassName, proxy);
}
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int id = bindAnnotation.value();
proxy.putElement(id, variableElement);
}
// 3.通过遍历mProxyMap,创建java文件 通过javapoet生成
for (String key : mProxyMap.keySet()) {
ClassCreatorProxy proxyInfo = mProxyMap.get(key);
JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
try {
// 生成文件
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
return true;
注:APT有一个局限性,就是只会扫描Java源码,不会扫描jar ,aar 和class 。也就是说,所有模块需要以源码形式存在。而现在通用的做法是,将模块打包成jar或者aar,发布到Maven库,再由其他模块自行引用,解决这个问题的基本方法用gradle plugin方法(项目中的medusa库就是插件的方式)