注解是什么
注解是一系列元数据,它提供数据用来解释程序代码,但是注解并非是所解释代码的一部分。注解对于代码的运行效果没有直接影响。
@Retention, @Target, @Inherited, @Documented,它们是用来定义 Annotation 的 Annotation。也就是当我们要自定义注解时,需要使用它们。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
@Retention
- @Retention(RetentionPolicy.SOURCE)
- 源码时注解,一般用来作为编译器标记。
- 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;源码注解(RetentionPolicy.SOURCE)的生命周期只存在Java源文件这一阶段,是3种生命周期中最短的注解。当在Java源程序上加了一个注解,这个Java源程序要由javac去编译,javac把java源文件编译成.class文件,在编译成class时会把Java源程序上的源码注解给去掉。需要注意的是,在编译器处理期间源码注解还存在,即注解处理器Processor 也能处理源码注解,编译器处理完之后就没有该注解信息了。
- @Retention(RetentionPolicy.RUNTIME)
- 运行时注解,在运行时通过反射去识别的注解@Retention(RetentionPolicy.RUNTIME)即可。
- 运行时注解一般和反射机制配合使用,相比编译时注解性能比较低,但灵活性好,实现起来比较简答
- @Retention(RetentionPolicy.CLASS)
- 编译时注解,在编译时被识别并处理的注解。
- 编译时注解能够自动处理Java源文件并生成更多的源码、配置文件、脚本或其他可能想要生成的东西。
//用来表示这个注解可以使用在哪些地方。比如:类、方法、属性、接口等等。
// 这里ElementType.TYPE 表示这个注解可以用来修饰:Class, interface or enum declaration。
// 当你用ContentView修饰一个方法时,编译器会提示错误。
@Target(ElementType.TYPE)
//用来修饰这是一个什么类型的注解。这里表示该注解是一个运行时注解。这样APT就知道啥时候处理这个注解了。
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
//返回值表示这个注解里可以存放什么类型值
int value();
}
什么是APT
AP: 就是Annotation Processing Tool的简称,叫做注解处理工具。就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。
原理: Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码.
我们来实现butterknife绑定view功能
第一步:定义注解 @BindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
第二步:实现 AbstractProcessor
-
新建一个Java lib
package com.xxw.compiler;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
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.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import static javax.lang.model.element.Modifier.*;
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({"com.xxw.compiler.BindView"})
public class MyProcessor extends AbstractProcessor {
// 存放同一个Class下的所有注解
Map> classMap = new HashMap<>();
// 存放Class对应的TypeElement
Map classTypeElement = new HashMap<>();
private Filer filer;
Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//初始化我们需要的基础工具
filer = processingEnv.getFiler();
elementUtils = processingEnv.getElementUtils();
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {//这里开始处理我们的注解解析了,以及生成Java文件
collectInfo(roundEnvironment);
writeToFile();
return true;
}
private void collectInfo(RoundEnvironment roundEnvironment) {
classMap.clear();
classTypeElement.clear();
//获取注解数量
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
// 获取 BindView 注解的值
int viewId = element.getAnnotation(BindView.class).value();
// 代表被注解的元素
VariableElement variableElement = (VariableElement) element;
//被注解元素所在的Class
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
// Class的完整路径
String classFullName = typeElement.getQualifiedName().toString();
// 收集Class中所有被注解的元素
List variableList = classMap.get(classFullName);
if (variableList == null) {
variableList = new ArrayList<>();
classMap.put(classFullName, variableList);
// 保存Class对应要素(名称、完整路径等)
classTypeElement.put(classFullName, typeElement);
}
VariableInfo variableInfo = new VariableInfo();
variableInfo.setVariableElement(variableElement);
variableInfo.setViewId(viewId);
variableList.add(variableInfo);
}
}
private void writeToFile() {
try {
for (String classFullName : classMap.keySet()) {
TypeElement typeElement = classTypeElement.get(classFullName);
// 使用构造函数绑定数据
MethodSpec.Builder constructor = MethodSpec.constructorBuilder()
.addModifiers(PUBLIC)
.addParameter(ParameterSpec.builder(TypeName.get(typeElement.asType()), "activity").build());
List variableList = classMap.get(classFullName);
for (VariableInfo variableInfo : variableList) {
VariableElement variableElement = variableInfo.getVariableElement();
// 变量名称(比如:TextView tv 的 tv)
String variableName = variableElement.getSimpleName().toString();
// 变量类型的完整类路径(比如:android.widget.TextView)
String variableFullName = variableElement.asType().toString();
// 在构造方法中增加赋值语句,例如:activity.tv = (android.widget.TextView)activity.findViewById(215334);
constructor.addStatement("activity.$L=($L)activity.findViewById($L)", variableName, variableFullName, variableInfo.getViewId());
}
// 构建Class
TypeSpec typeSpec = TypeSpec.classBuilder(typeElement.getSimpleName() + "$$ViewInjector")
.addModifiers(PUBLIC)
.addMethod(constructor.build())
.build();
// 与目标Class放在同一个包下,解决Class属性的可访问性
String packageFullName = elementUtils.getPackageOf(typeElement).getQualifiedName().toString();
JavaFile javaFile = JavaFile.builder(packageFullName, typeSpec)
.build();
// 生成class文件
javaFile.writeTo(filer);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后在app 工程下引入
@BindView(R.id.tv)
TextView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectHelper.inject(this);
mView.setText(" tv is injected");
}
调试apt代码
第一步在init打断点
第二步在自己的.gradle 目录下gradle.properties,没有就自己建一个,加上下面代码
org.gradle.daemon=true
org.gradle.jvmargs=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
1、找到:Select Run/Debug Configration -> Edit Configrations...,
2、在面板上添加Remote ,随便命名,其他都用默认
然后调试你的代码吧,如果端口被占用,先关掉。
可以试试自己实现onclick