APT 及实现ButterKnife
- 1. 译时和运行时注解
- 2. 什么是 APT
- 3. AbstractProcessor
- 4. 结构体语言 -- Java
- 4.1 Java -- Element 和 Elements
- 5. 自定义编译注解处理器
- 5.1 在 annotation module 中定义注解
- 5.2 在 compiler module 中添加 注解处理器 Processor
- 5.3 在 library module 中创建要生成的文件
- 5.4 使用
- 5.5 编译后生成的代码
了解 APT
的相关知识,相信手动实践后你会更容易理解像 Dagger
、ARouter
、ButterKnife
等这种使用了编译时注解的框架,也更容易理解其内部源码实现,内容如下:
- 编译时和运行时注解
- 注解处理器APT
- AbstractProcessor
- Element和Elements
- 自定义注解处理器
- 使用自定义注解处理器
- 编译时和运行时注解
1. 译时和运行时注解
先了解一下编译时和运行时的区别:
- 编译时:指编译器将源代码翻译成机器能够识别的代码的过程,Java 中也就是将 Java 源代码编译为 JVM 识别的字节码文件的过程。
- 运行时:指 JVM 分配内存、解释执行字节码文件的过程。
元注解 @Retention
决定注解是在编译时还是在运行时,其可配置策略如下:
public enum RetentionPolicy {
SOURCE, //在编译时会被丢弃,仅仅在源码中存在
CLASS, //默认策略,运行时就会被丢弃,仅仅在 class 文件中
RUNTIME //编译时会将注解信息记录到class文件,运行时任然保留,可以通过反射获取注解信息
}
编译时注解和运行时注解除了上面的区别之外,实现方式也不一样:
- 编译时注解一般是通过注解处理器(APT)来实现的
- 运行时注解一般是通过反射来实现的。
2. 什么是 APT
APT (Annotation Processing Tool)
是一种处理注释的工具,它对源代码文件进行检测并找出其中的 Annotation,根据注解自动生成代码,如果想要自定义的注解处理器能够运行,必须要通过 APT 工具来处理。
简单说:根据规则,帮我们生成代码、生成类文件
编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ButterKnife
、Dagger
、ARouter
等
3. AbstractProcessor
AbstractProcessor
实现 Processor
是注解处理器的抽象类,要实现注解处理器都需要继承 AbstractProcessor
进行扩展,主要方法说明如下:
- init:初始化Processor,可从参数 ProcessingEnvironment 中获取工具类 Elements、Types、Filer 以及 Messager 等;
- getSupportedSourceVersion:返回使用的 Java 版本;
- getSupportedAnnotationTypes:返回要处理的的所有的注解名称;
- process:获取所有指定的注解进行处理;
- process 方法可能会在运行过程中执行多次,知道没有其他类产生为止。
4. 结构体语言 – Java
eg: html 就是一种 element 组成的结构体
对于 Java 源文件来说,它同样也是一种结构体语言
// PackageElement
package manu.com.compiler;
// TypeElement
public class ElementSample {
// VariableElement
private int a;
// VariableElement
private Object other;
// ExecutableElement
public ElementSample() {
}
// 方法参数VariableElement
public void setA(int newA) {
}
// TypeParameterElement表示参数化类型,用在泛型参数中
}
4.1 Java – Element 和 Elements
Element
类似于 XML 中的标签一样,在 Java 中 Element
表示程序元素,如类、成员、方法等,每个 Element
都表示仅表示某种结构,对 Element
对象类的操作,请使用 visitor
或 getKind()
方法进行判断再具体处理,Element 的子类如下:
- ExecutableElement
- PackageElement
- Parameterizable
- QualifiedNameable
- TypeElement
- TypeParameterElement
- VariableElement
Elements
是处理 Element
的工具类,只提供接口,具体有 Java 平台实现
5. 自定义编译注解处理器
下面使用 APT
实现一个自定义的注解 @BindView
来替换来模仿 ButterKnife
的注解 @BindView
,案例项目结构如下:
5.1 在 annotation module 中定义注解
// SOURCE 注解仅在源码中保留,class文件中不存在
// CLASS 注解在源码和class文件中都存在,但运行时不存在
// RUNTIME 注解在源码,class文件中存在且运行时可以通过反射机制获取到
@Target(ElementType.FIELD) // 注解作用在属性之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface BindView {
// 返回R.id.xx值
int value();
}
@Target(ElementType.METHOD) // 注解作用在方法之上
@Retention(RetentionPolicy.CLASS) // 编译期原理(交予注解处理器)
public @interface OnClick {
// 此处省略了int[]
int value();
}
5.2 在 compiler module 中添加 注解处理器 Processor
引入 Google 的 auto-service
用来生成 META-INFO
下的相关文件,javapoet
用于更方便的创建 Java
文件,自定义注解处理器 如下:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
// 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.10.0"
// 引入annotation,处理@BindView、@Onclick注解
implementation project(':annotation')
}
// 中文乱码问题(错误: 编码GBK的不可映射字符)
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.BINDVIEW_ANNOTATION_TYPES, Constants.ONCLICK_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class ButterKnifeProcessor extends AbstractProcessor {
// 操作Element工具类 (类、函数、属性都是Element)
private Elements elementUtils;
// type(类信息)工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
private Filer filer;
// key:类节点, value:被@BindView注解的属性集合
private Map<TypeElement, List<VariableElement>> tempBindViewMap = new HashMap<>();
// key:类节点, value:被@OnClick注解的方法集合
private Map<TypeElement, List<ExecutableElement>> tempOnClickMap = new HashMap<>();
// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// 初始化
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
messager.printMessage(Diagnostic.Kind.NOTE,
"注解处理器初始化完成,开始处理注解------------------------------->");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
// 一旦属性上使用了@BindView注解
if (!EmptyUtils.isEmpty(set)) {
// 获取所有被 @BindView注解的 属性元素集合
Set<? extends Element> bindViewElements = roundEnv.getElementsAnnotatedWith(BindView.class);
// 获取所有被 @OnClick注解的 方法元素集合
Set<? extends Element> onClickElements = roundEnv.getElementsAnnotatedWith(OnClick.class);
if (!EmptyUtils.isEmpty(bindViewElements) || !EmptyUtils.isEmpty(onClickElements)) {
// 收集信息,存储到temp集合中。用来生成代码
valueOfMap(bindViewElements, onClickElements);
try {
// 生成类文件
createJavaFile();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
/**
* 收集信息,存储Map赋值
*/
private void valueOfMap(Set<? extends Element> bindViewElements, Set<? extends Element> onClickElements) {
if (!EmptyUtils.isEmpty(bindViewElements)) {
for (Element element : bindViewElements) {
messager.printMessage(Diagnostic.Kind.NOTE, "@BindView >> " + element.getSimpleName());
if (element.getKind() == ElementKind.FIELD) {
VariableElement fieldElement = (VariableElement) element;
// 属性节点,上一个(父节点),类节点
TypeElement typeElement = (TypeElement) fieldElement.getEnclosingElement();
// 如果map集合中包含了key(类节点)
if (tempBindViewMap.containsKey(typeElement)) {
tempBindViewMap.get(typeElement).add(fieldElement);
} else {
List<VariableElement> fields = new ArrayList<>();
fields.add(fieldElement);
tempBindViewMap.put(typeElement, fields);
}
}
}
}
if (!EmptyUtils.isEmpty(onClickElements)) {
for (Element element : onClickElements) {
messager.printMessage(Diagnostic.Kind.NOTE, "@OnClick >> " + element.getSimpleName());
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement methodElement = (ExecutableElement) element;
// 属性节点,上一个(父节点),类节点
TypeElement typeElement = (TypeElement) methodElement.getEnclosingElement();
// 如果map集合中包含了key(类节点)
if (tempOnClickMap.containsKey(typeElement)) {
tempOnClickMap.get(typeElement).add(methodElement);
} else {
List<ExecutableElement> methods = new ArrayList<>();
methods.add(methodElement);
tempOnClickMap.put(typeElement, methods);
}
}
}
}
}
private void createJavaFile() throws IOException {
// 判断是否有需要生成的类文件(有坑)
if (!EmptyUtils.isEmpty(tempBindViewMap)) {
// 获取接口的类型
TypeElement viewBinderType = elementUtils.getTypeElement(Constants.VIEWBINDER);
TypeElement clickListenerType = elementUtils.getTypeElement(Constants.CLICKLISTENER);
TypeElement viewType = elementUtils.getTypeElement(Constants.VIEW);
// 从下往上写(JavaPoet技巧)
for (Map.Entry<TypeElement, List<VariableElement>> entry : tempBindViewMap.entrySet()) {
// 类名(TypeElement)
ClassName className = ClassName.get(entry.getKey());
// 实现接口泛型(implements ViewBinder<MainActivity>)
ParameterizedTypeName typeName = ParameterizedTypeName.get(ClassName.get(viewBinderType),
ClassName.get(entry.getKey()));
// 方法参数体
ParameterSpec parameterSpec = ParameterSpec.builder(ClassName.get(entry.getKey()), // MainActivity
Constants.TARGET_PARAMETER_NAME) // 方法参数名target
.addModifiers(Modifier.FINAL) // 参数修饰符
.build(); // 参数体构建完成
// 方法体:public void bind(final MainActivity target) {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constants.BIND_METHOD_NAME) // bind方法名
.addAnnotation(Override.class) // 接口重写方法
.addModifiers(Modifier.PUBLIC) // 方法修饰符
.addParameter(parameterSpec); // 方法参数
for (VariableElement fieldElement : entry.getValue()) {
// 获取属性名
String fieldName = fieldElement.getSimpleName().toString();
// 获取@BindView注解的值
int annotationValue = fieldElement.getAnnotation(BindView.class).value();
// target.tv = target.findViewById(R.id.tv);
String methodContent = "$N." + fieldName + " = $N.findViewById($L)";
// 加入方法内容
methodBuilder.addStatement(methodContent,
Constants.TARGET_PARAMETER_NAME, // target
Constants.TARGET_PARAMETER_NAME, // target
annotationValue); // R.id.xx
}
if (!EmptyUtils.isEmpty(tempOnClickMap)) {
for (Map.Entry<TypeElement, List<ExecutableElement>> methodEntry : tempOnClickMap.entrySet()) {
// 类名
if (className.equals(ClassName.get(methodEntry.getKey()))) {
for (ExecutableElement methodElement : methodEntry.getValue()) {
// 获取方法名
String methodName = methodElement.getSimpleName().toString();
// 获取@OnClick注解的值
int annotationValue = methodElement.getAnnotation(OnClick.class).value();
/**
* target.findViewById(R.id.tv).setOnClickListener(new DebouncingOnClickListener() {
*
* @Override
* public void doClick(View v) {
* target.click(v);
* }
* });
*/
methodBuilder.beginControlFlow("$N.findViewById($L).setOnClickListener(new $T()",
Constants.TARGET_PARAMETER_NAME, annotationValue, ClassName.get(clickListenerType))
.beginControlFlow("public void doClick($T v)", ClassName.get(viewType))
.addStatement("$N." + methodName + "(v)", Constants.TARGET_PARAMETER_NAME)
.endControlFlow()
.endControlFlow(")")
.build();
}
}
}
}
// 生成必须是同包:(属性的修饰符是缺失的)
JavaFile.builder(className.packageName(), // 包名
TypeSpec.classBuilder(className.simpleName() + "$ViewBinder") // 类名
.addSuperinterface(typeName) // 实现ViewBinder接口(有泛型)
.addModifiers(Modifier.PUBLIC) // 类修饰符
.addMethod(methodBuilder.build()) // 加入方法体
.build()) // 类构建完成
.build() // JavaFile构建
.writeTo(filer); // 文件生成器开始生成类文件
}
}
}
}
5.3 在 library module 中创建要生成的文件
/**
* 核心类,接口 = 接口实现类
* ButterKnife用的是构造方法.newInstance()
* 接口.bind()
*/
public class ButterKnife {
public static void bind(Activity activity) {
// 拼接类名,如:MainActivity$ViewBinder
String className = activity.getClass().getName() + "$ViewBinder";
try {
// 加载上述拼接类
Class<?> viewBinderClass = Class.forName(className);
// 接口 = 接口实现类
ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance();
// 调用接口方法
viewBinder.bind(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 点击监听接口,实现类(抽象类 + 抽象方法)
public abstract class DebouncingOnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
// 调用抽象方法
doClick(v);
}
public abstract void doClick(View v);
}
/**
* 接口绑定类(所有注解处理器生的类,都需要实现该接口,= 接口实现类)
*/
public interface ViewBinder<T> {
void bind(T target);
}
5.4 使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@BindView(R.id.tv)
TextView tv2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.tv)
public void click(View view) {
Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.tv)
public void click2(View view) {
Toast.makeText(this, tv.getText().toString(), Toast.LENGTH_SHORT).show();
}
}
5.5 编译后生成的代码
public class MainActivity$ViewBinder implements ViewBinder<MainActivity> {
@Override
public void bind(final MainActivity target) {
target.tv = target.findViewById(2131165325);
target.tv2 = target.findViewById(2131165325);
target.findViewById(2131165325).setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View v) {
target.click(v);
}
} );
target.findViewById(2131165325).setOnClickListener(new DebouncingOnClickListener() {
public void doClick(View v) {
target.click2(v);
}
} );
}
}