APT 简介
APT 就是注解处理器,他是 javac 的一个工具,用来在编译时扫描和处理注解。一个注解处理器它以 Java 代码作为输入,生成文件(通常是 java 文件),这些生成的 java 文件不能修改,并且会和我们手动编写的 java 文件一样会被 javac 进行编译。
这里我们简单模拟一下 ButterKnife 的原理,来讲解 APT.
简单使用
首先我们新建一个项目,在项目中新建一个 Java Module,命名为 annotation,用于声明注解,然后在 annotation Module 中新建一个元注解类命名为 BindView
package com.radish.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//作用在属性上
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
然后我们再新建一个 Java Module 命名为 compiler(注意不是 Android Librarry,是 Java Library)
然后在 compiler Moodule 中新建一个类,命名为 TestProcessor 并且 extends AbstractProcessor。
首先,我们需要将我们自定义的注解处理器进行注册,在 build.gradle 中添加配置,然后修改代码
compile 'com.google.auto.service:auto-service:1.0-rc2'
package com.radish.compiler;
import com.google.auto.service.AutoService;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
//自动注册
@AutoService(Processor.class)
public class TestProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
接下来,我们需要指定一些配置,比如说,该注解处理器可以处理什么样的注解,Java 版本是多少,初始化一些工具类等等。
package com.radish.compiler;
import com.google.auto.service.AutoService;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
//自动注册
@AutoService(Processor.class)
//可以处理的注解,必须是全类名
@SupportedAnnotationTypes({"com.radish.annotation.BindView"})
//编译时候的 Java 版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class TestProcessor extends AbstractProcessor {
/**
* 日志工具
*/
private Messager messager;
/**
* 文件工具
*/
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
}
/**
*
* @param annotations 使用了当前注解处理器允许处理的注解的节点集合
* 那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
* @param roundEnv 环境
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
接下来我们就需要了解最主要的一个方法 process,该法方法是一定会被调用的方法,就类似于 Java 中的 main 方法
/**
*
* @param annotations 使用了当前注解处理器允许处理的注解的节点集合
* 那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
* @param roundEnv 环境
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for(TypeElement typeElement : annotations){
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
for(Element element : elements){
messager.printMessage(Diagnostic.Kind.NOTE,element.getSimpleName());
}
}
return false;
}
然后,我们在 app Module 中引入 annotation Module:
//是 apt 中的一种工具,是 google 开发的内置框架,不需要引入,直接在 build.gralde 文件中引入
annotationProcessor project(':compiler')
compile project(':annotation')
这样就可以开始使用注解了
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
然后我们 make 一下 app
image.png
目前我们 process 中拿到的被注解的节点是 tv
/**
*
* @param annotations 使用了当前注解处理器允许处理的注解的节点集合
* 那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
* @param roundEnv 环境
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for(TypeElement typeElement : annotations){
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
for(Element element : elements){
// messager.printMessage(Diagnostic.Kind.NOTE,element.getSimpleName());
//获取其父节点(tv 的父节点:MainActivity)
Element enclosingElement = element.getEnclosingElement();
/*
假如我们需要创建一个类,假如叫 MainActivity_Binding
enclosingElement.getSimpleName().toString() + "_Binding";
但是类里面的代码,都需要字符串的拼接,比较繁琐而且容易出错,
那么我们就可以借助 javapoet 来进行 java 文件的创建
*/
}
}
return false;
}
JavaPoet
在 build.gradle 添加一行配置compile 'com.squareup:javapoet:1.7.0'
,其github 的网址是:
/**
*
* @param annotations 使用了当前注解处理器允许处理的注解的节点集合
* 那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
* @param roundEnv 环境
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for(TypeElement typeElement : annotations){
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
for(Element element : elements){
//获取其父节点(tv 的父节点:MainActivity)
Element enclosingElement = element.getEnclosingElement();
String className = enclosingElement.getSimpleName() + "_Binding";
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
然后我们 make app,java 文件就生成了。
image.png
package com.example.helloworld;
import java.lang.String;
import java.lang.System;
public final class MainActivity_Binding {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
然后我们就可以在我们的类中去使用这个生成的 java 类了。
image.png
如何调试注解处理器
image.pngimage.png
image.png
image.png
image.png
image.png
image.png
然后就可以开始 debug 调试模式了
网友评论