简介
注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。也就是说,在编译阶段我们就可以获取到源文件内注解(Annotation)相关内容。
作用
由于注解处理器是在编译时期工作,因此我们可以在编译时期通过注解处理器进行某些行为。比较常用的就是在编译时期访问注解的数据,然后动态生成java文件。典型案例就是butterknife注解工具类。
Annotation Processor用法
使用Annotation Processor之前我们先来简单了解一下元注解。
注解
元注解是java提供的最基础的注解,负责注解其他的注解,它们是用来解释其他注解的,在java.lang.annotation包下。
元注解有:
@Retention:注解保留的生命周期
@Target:注解对象的作用范围。
@Inherited:@Inherited标明所修饰的注解,在所作用的类上,是否可以被继承。
@Documented:如其名,javadoc的工具文档化,基本不关心。
@Retention
Retention标明了注解的生命周期,对应RetentionPolicy的枚举,表示注解在何时生效:
SOURCE:只在源码中有效,编译时抛弃。
CLASS:编译class文件时生效。
RUNTIME:运行时才生效。
@Target
Target标明了注解的适用范围,对应ElementType枚举,明确了注解的有效范围。
TYPE:类、接口、枚举、注解类型。
FIELD:成员变量
METHOD:方法
PARAMETER:参数
CONSTRUCTOR:构造器
LOCAL_VARIABLE:局部变量
ANNOTATION_TYPE:注解类型
PACKAGE:包
TYPE_PARAMETER:类型参数
TYPE_USE:类型使用声明
@Inherited
注解所作用的类,在继承时默认无法继承
父类的注解。除非注解声明了 @Inherited。同时Inherited声明出来的注,只对类有效,对方法/属性无效。
自定义注解
了解了元注解后,看看如何实现和使用自定义注解。下面实现BindView 注解作用在成员变量上面,并且只在源码中生效,编译时丢弃。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
int value();
}
更改适用范围和生命周期只需要更改对应Target和Retention注解的值即可。
Annotation Processor使用
使用有两个步骤:
1.自定义注解类型。
2.注册注解处理器完成对注解的处理
步骤一前面已经看过不在多说,下面我们直接来看第二个步骤:
注册注解处理器前,我们需要完成注解处理器的编码工作:
1.继承AbstractProcessor类并重写对应的方法
public class CusComplier extends AbstractProcessor {
private Elements elementUtils;
private Filer filer;
private Messager messager;
private Types typeUtils;
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//Element代表程序的元素,例如包、类、方法。
elementUtils = processingEnvironment.getElementUtils();
//文件生成处理的类
filer = processingEnvironment.getFiler();
//日志信息
messager = processingEnvironment.getMessager();
////处理TypeMirror的工具类,用于取类信息
typeUtils = processingEnvironment.getTypeUtils();
}
@Override
public Set getSupportedAnnotationTypes() {
Set set = new LinkedHashSet<>();
set.add(BindView.class.getCanonicalName());
return set;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
2.注册注解处理器
注册方式有2中,第一种是手动在\resources\META-INF.services\javax.annotation.processing.Processor
文件下输入全名称
的处理器名。
第二种是比较推荐的自动注册方式
gradle中引入一下依赖
implementation 'com.google.auto.service:auto-service:1.0-rc6'
然后在类名前加入一下注解,如果不想重写getSupportedSourceVersion
和getSupportedAnnotationTypes
,也可以通过注解方式完成。
@AutoService(Processor.class)//自动注册
@SupportedAnnotationTypes({"com.xj.annotation.BindView","com.xj.annotation.OnClick"})//支持注解类型
@SupportedSourceVersion(SourceVersion.RELEASE_7)//支持版本
public class CusComplier extends AbstractProcessor {}
编译时,会自动去查找AutoService注解,从而完成注册。
下面我们来看一下process方法:
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment)
process方法
是处理器实际处理逻辑入口,该方法能够获取到支持注解的所有数据。其中set是支持注解类型的集合,RoundEnvironment 是所有扫描到的元素的数据集合。因此我可以通过这两个参数操作注解数据,大致操作的流程如下:
1.遍历获取到的源码集合,获取需要的注解数据。
2.判断元素是否满足条件
3.组织数据结构相关参数
4.依据元素j组织ava类型文件,并写入到文件中
5.相关的错误信息。
下面是仿BindView的注解处理类:
@AutoService(Processor.class)
@SupportedAnnotationTypes({"com.xj.annotation.BindView","com.xj.annotation.OnClick"})
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationComplier extends AbstractProcessor {
...
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE,"process");
//支持的注解类型
for (TypeElement typeElement: set){
messager.printMessage(Diagnostic.Kind.NOTE,"process"+typeElement.toString());
//返回使用对应注解的元素
Set extends Element> elementsAnnotateds = roundEnvironment.getElementsAnnotatedWith(typeElement);
for (Element element : elementsAnnotateds){
//返回元素的里层元素 此时返回的是类元素
Element enclosingElement = element.getEnclosingElement();
TypeMirror typeMirror = enclosingElement.asType();
//获取类名
String className = typeMirror.toString();
//业务处理代码 将元素依据类进行分类,便于生成代码的时候直接写在同一个类中
ArrayList elements = mapElements.get(className);
if (null == elements){
elements = new ArrayList<>();
elements.add(element);
mapElements.put(className,elements);
} else {
elements.add(element);
}
}
}
writeJavaClass();
return false;
}
/**
*编写java代码
*/
private void writeJavaClass() {
//APT 方式写代码
Set>> mapSet = mapElements.entrySet();
for (Map.Entry> entrys : mapSet){
String className = entrys.getKey();
ArrayList elements = entrys.getValue();
TypeElement classElement = elementUtils.getTypeElement(className);
String pageName = elementUtils.getPackageOf(classElement).getQualifiedName().toString();
StringBuilder builder = new StringBuilder();
builder.append("package ").append(pageName).append(";").append("\n").append("\n");
builder.append("import android.support.annotation.UiThread;").append("\n");
builder.append("import android.view.View;").append("\n");
builder.append("import java.lang.Override;").append("\n");
builder.append("import com.xj.annotationcore.Unbinder;").append("\n").append("\n");
builder.append("public ").append("class ").append(classElement.getSimpleName()).append("_ViewBinding").append(" implements")
.append(" Unbinder,").append(" View.OnClickListener").append("{").append("\n").append("\n");
builder.append("private").append(" ").append(classElement.getSimpleName()).append(" ").append("target;").append("\n").append("\n");
builder.append("@UiThread").append("\n");
builder.append("public void ").append(classElement.getSimpleName()).append("_ViewBinding(").append("final ").append(classElement.getSimpleName())
.append(" target , ").append("View source) {").append("\n");
builder.append("this.target = target;").append("\n");
ArrayList mElementName = new ArrayList<>();
//依据元素生成不同的代码
for (Element element: elements){
messager.printMessage(Diagnostic.Kind.NOTE,"======write======"+element.getSimpleName());
OnClick onClick = element.getAnnotation(OnClick.class);
if (null != onClick){
int[] ids = onClick.value();
for (int id : ids ){
builder.append("source.findViewById(").append(id).append(")")
.append(".setOnClickListener(this);").append("\n");
}
}
BindView bindView = element.getAnnotation(BindView.class);
if (null != bindView){
messager.printMessage(Diagnostic.Kind.NOTE,"======write======bindView ");
//注解的值,此时就是id
int id = bindView.value();
//元素名称,此时就是生命的变量名称
Name simpleName = element.getSimpleName();
mElementName.add(simpleName.toString());
builder.append("target.").append(simpleName).append(" = ").append("source.findViewById(").append(id).append(")").append(";").append("\n");
}
}
builder.append("}").append("\n");
//onClick
builder.append("@Override\n").append("public void onClick(View view) {\n").append("this.target.onClick(view);\n").append("}").append("\n");
//unbind
builder.append("public void unbind() {").append("\n").append("MainActivity target = this.target;").append("\n")
.append("this.target = null;").append("\n");
for (String name : mElementName){
builder.append("target.").append(name).append(" = null;").append("\n");
}
builder.append("}");
builder.append("}");
try {
messager.printMessage(Diagnostic.Kind.NOTE,"string "+builder.toString());
//新建类,通过Writer将写好的builder代码字串输入进去从而完成代码书写、
JavaFileObject object = filer.createSourceFile(pageName+"."+classElement.getSimpleName()+"_ViewBinding");
Writer writer = object.openWriter();
writer.append(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
其中生成代码过程比较繁琐,也可使用javapoet工具来替代,javapoet工具
是封装了生成代码的过程,极大的减少编写压力。javapoet中简单代码介绍:
//节点元素,用来生成类
typeSpecBuilder = TypeSpec.classBuilder(classTypeElement.getSimpleName() + "_ViewBinding");
//生成参数
ParameterSpec targetParameterSpec = ParameterSpec
.builder(ClassName.get(classTypeElement), "target", Modifier.FINAL)
.build();
//生成构造函数
constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(targetParameterSpec)
.addParameter(viewParameterSpec)
.addAnnotation(ClassName.bestGuess("android.support.annotation.UiThread"))
.addModifiers(Modifier.PUBLIC);
// 添加代码块
constructorMethodBuilder.addStatement("this.target = target");
//普通方法生成
MethodSpec.methodBuilder("unbind")
.addModifiers(Modifier.PUBLIC);
//生成MainActivity_ViewBinding类
TypeSpec typeSpec = typeSpecBuilder.addField(ClassName.get(classTypeElement), "target", Modifier.PRIVATE)
.addMethod(constructor)
.addMethod(unbindMethodSpec.build())
.addSuperinterface(ClassName.bestGuess("com.xj.annotationcore.Unbinder"))
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ClassName.bestGuess("View.OnClickListener"))
.build();
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.build();
try {
//写入java文件
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
详细参数介绍请查看GitHub相关库信息。
Butterknife总结
使用Butterknife
1.gradle引入Butterknife库
dependencies {
implementation 'com.jakewharton:butterknife:10.2.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.0'
}
2.注册当前界面到Butterknife
ButterKnife.bind(this);
bind参数有activity,view,object...
3.使用
@BindView(R.id.tv)
TextView textView;
@OnClick({R.id.tv})
public void onClick(View view){}
等等
4.解绑
Unbinder bind = ButterKnife.bind(this);
bind.unbind();
5.原理
Butterknife基于APT技术,在编译时处理对应的注解信息动态生成相应的代码写入到xxx__ViewBinding
文件中。然后通过bind
方法完成绑定。
@BindView(R.id.tv)
上面的注解会在对应文件中生成:
View view = source.findViewById(id);
动态生成的方法和原生方式并没有不同,但因为是自动生成的所以极大地简化我们书写这类重复的代码。
ButterKnife.bind(this);
static final Map, Constructor extends Unbinder>> BINDINGS = new LinkedHashMap<>();
@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView();
return bind(target, sourceView);
}
@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class> targetClass = target.getClass();
Constructor extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
...
if (constructor == null) {
return Unbinder.EMPTY;
}
return constructor.newInstance(target, source);
...
}
}
@Nullable @CheckResult @UiThread
private static Constructor extends Unbinder> findBindingConstructorForClass(Class> cls) {
Constructor extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
if (debug) Log.d(TAG, "HIT: Cached in binding map.");
return bindingCtor;
}
String clsName = cls.getName();
try {
Class> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
bindingCtor = (Constructor extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} ...
INDINGS.put(cls, bindingCtor);
return bindingCtor;
}
Bind方法调用流程:
1.从静态Map中获取构造方法实例,有则说明已经绑定过直接返回。没有则说明需要重新绑定,因此会获取xxx_ViewBinding类,获取其构造方法实例,设置到Map中并返回。
2.拿到构造方法实例,通过反射传入界面与当前根布局,完成绑定。
自动生成的类中获取到了对应的界面与根布局就可以做它想做的操作了。