文章目录
什么是注解处理器(Annotation Processing Tool)
注解相关的内容单独整理了笔记 Java注解学习记录(反射也能实现ButterKnife)
注解处理器简称APT,它是javac的一个工具。(APT是在Java1.5引入的,因为注解是在1.5引入的)
作用:用来在编译时扫描和处理注解。
应用场景:在框架中广泛运用,比如:ButterKnife、EventBus、Dagger2、ARouter…
注解处理器怎么用
step1. 创建Java Library
创建两个Java Library
annotation module //存放注解类
compiler module //存放注解处理器类
//添加依赖
implementation project(':annotation')
implementation 'com.squareup:javapoet:1.11.1' //方便构建java类的第三方库
app module //使用注解的模块
//添加依赖
implementation project(':annotation')
annotationProcessor project(':compiler')
//添加配置
android{
defaultConfig {
...
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
}
}
step2. 创建注解类
@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.FIELD)
public @interface BindView {
int id() default -1;
}
step3. 使用注解
public class MainActivity extends Activity {
@BindView(id = R.id.btn)
public Button btn;
@BindView(id = R.id.btn2)
public Button btn2;
}
step4. 创建注解处理器类
注解处理器类都必须继承AbstractProcessor
public class BindCompiler extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> set = new HashSet<>();
set.add(BindView.class.getCanonicalName());
return set;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//遍历所有类中用到BindView注解的元素
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)){
String fieldName = element.getSimpleName().toString(); //属性名
int id = element.getAnnotation(BindView.class).id(); //id
}
return false;
}
}
step5. 声明注解处理类
注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容javax.annotation.processing.Processor用于替换JDK6之前的APT(Annotatino Processing Tool)
方法一、手动生成Processor文件
创建Processor文件:src/main/resources/META-INF/services/javax.annotation.processing.Processor
在Processor文件填写内容:注解处理器路径:包名 + 继承extends AbstractProcessor的类名
方法二、依赖auto-service库自动生成
implementation ‘com.google.auto.service:auto-service:1.0-rc4’
可使用@AutoService(Process.class)自动生成
@AutoService(Process.class)
public class BindCompiler extends AbstractProcessor
注意:有坑!@AutoService(Process.class) 高版本gradle(5+)没有自动生成文件
Element接口
Element是一个接口,它只在编译期存在。Element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。
Element子接口
ExecutableElement:表示方法体
PackageElement :表示包信息
TypeElement:表示类或接口
TypeParameterElement:表示类、接口、方法的泛型类型例如T。
VariableElement:表示字段,枚举常量,方法或者构造函数参数,局部变量,资源变量或者异常参数。
Element接口和Type接口之间有什么区别呢?
Element所代表的元素只在编译期可见,用于保存元素在编译期的各种状态。
Type所代表的元素是运行期可见,用于保存元素在编译期的各种状态。
Element常用API
public interface AnnotatedConstruct {
//AnnotationMirror 注解信息类
//AnnotationMirror#DeclaredType getAnnotationType() 返回该注解的详细信息
//AnnotationMirror#Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() 返回该注解的值
//Annotation 注解类
//AnnotationMirror#Class<? extends Annotation> annotationType() 返回注解Class对象
//获取所有的注解类型信息
List<? extends AnnotationMirror> getAnnotationMirrors();
//获取指定类型的注解
<A extends Annotation> A getAnnotation(Class<A> var1);
//获取所有指定类型的注解
//如果如果使用了元注解@Repeatable(Java1.8)的话,能实现同一个元素上有多个相同的注解
<A extends Annotation> A[] getAnnotationsByType(Class<A> var1);
}
public interface Element extends AnnotatedConstruct {
//获取元素的类型信息
//例如获取类型名称 TypeName typeName = ClassName.get(element.asType());
TypeMirror asType();
//获取元素类型,判断是哪种Element
ElementKind getKind();
//获取元素作用域,public static final等关键字
Set<Modifier> getModifiers();
//获取名字,不带包名
Name getSimpleName();
//获取父Element
//比如 ExecutableElement的父为TypeElemnt,TypeElemnt的父为PackageElment,PackageElment的父为null
Element getEnclosingElement();
//获取子Element
//比如 PackageElement它可能包含TypeElement,TypeElement它可能包含VariableElement、ExecutableElement
List<? extends Element> getEnclosedElements();
//获取元素上的所有注解的注解信息
List<? extends AnnotationMirror> getAnnotationMirrors();
//获取指定类型的注解
<A extends Annotation> A getAnnotation(Class<A> var1);
}
public interface TypeElement extends Element, Parameterizable, QualifiedNameable {
//返回此类型元素的嵌套种类(外部类、内部类、匿名类...)
NestingKind getNestingKind();
//返回完整名(包 + 类名)
Name getQualifiedName();
//获取父类信息
TypeMirror getSuperclass();
//返回继承的类以及所有实现的接口
List<? extends TypeMirror> getInterfaces();
//获取所有泛型参数
List<? extends TypeParameterElement> getTypeParameters();
}
public interface TypeParameterElement extends Element {
//返回由此类型参数参数化的一般类、接口、方法或构造方法
Element getGenericElement();
//返回此类型参数的边界
List<? extends TypeMirror> getBounds();
}
public interface VariableElement extends Element {
//如果此变量是一个被初始化为编译时常量的 static final 字段,则返回此变量的值。
//反之为null
Object getConstantValue();
}
public interface ExecutableElement extends Element, Parameterizable {
//获取所有泛型参数
List<? extends TypeParameterElement> getTypeParameters();
//获取返回值
TypeMirror getReturnType();
//获取所有传参
List<? extends VariableElement> getParameters();
//如果此方法或构造方法接受可变数量的参数,则返回 true,否则返回 false。
boolean isVarArgs();
//按声明顺序返回此方法或构造方法的 throws 子句中所列出的异常和其他 throwable。
List<? extends TypeMirror> getThrownTypes();
}
AbstractProcessor怎么用
public class BindCompiler extends AbstractProcessor {
private Messager messager; //用于打印信息
private Elements elementUtils; // Element处理工具类,获取Element的信息
private Filer filer; // 文件管理工具类,可以用于生成java源文件
private Types typeUtils; // 类型处理工具类
/**
* 初始化方法
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
typeUtils = processingEnvironment.getTypeUtils();
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
}
/**
* 返回此注释 Processor 支持的Java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 返回需要处理的注解集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return null;
}
/**
* 注解处理器的核心方法,处理具体的注解
*
* 扫描所有类,获取所有存在指定注解的字段
* @param set 扫描所有类文件,返回我们关注的注解类型(都是getSupportedAnnotationTypes方法里边配置的,只有用到的才返回)
* @param roundEnvironment 扫描所有类文件,获取所有存在指定注解的字段
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
实战:APT实现乞丐版ButterKnife
先康康ButterKnife源码
//就一个方法就能帮我们findViewById、setOnClickListener???怎么做到的,黑人问号?
ButterKnife.bind(MainActivity.this);
//ButterKnife#bind
public static Unbinder bind(@NonNull Activity target) {
View sourceView = target.getWindow().getDecorView(); //拿到根view(我们所有的view都是add到这里的)
return bind(target, sourceView);
}
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
Class<?> targetClass = target.getClass();
//寻找实现Unbinder接口的类的构造函数。
//( ̄口 ̄)!!可我的MainActivity没实现Unbinder啊
Constructor<? extends Unbinder> constructor = findBindingConstructorForClass(targetClass);
if (constructor == null) {
return Unbinder.EMPTY;
}
try {
return constructor.newInstance(target, source);
}
//...省略一大波catch
}
//ButterKnife#findBindingConstructorForClass
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
//先从缓存中获取,有就直接返回缓存的
Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls);
if (bindingCtor != null || BINDINGS.containsKey(cls)) {
return bindingCtor;
}
//...
try {
//( ̄口 ̄)!! 添油加醋?怎么还加个_ViewBinding
//( ̄口 ̄)!! MainActivity_ViewBinding怎么来的?我没有创建过这个类啊!绝对是ButterKnife干的好事
//这里是将MainActivity_ViewBinding类加载到类加载器里,
Class<?> bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding");
bindingCtor = (Constructor<? extends Unbinder>) bindingClass.getConstructor(cls, View.class);
} catch (ClassNotFoundException e) {
//找不到?那就拿父类来继续找
bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
}
//添加到缓存
BINDINGS.put(cls, bindingCtor);
return bindingCtor;
}
吃鲸,这家伙bind()方法的工作就是实例化了MainActivity_ViewBinding类。MainActivity_ViewBinding从何而来?我去apk康康有没有这个类。
还真有。。。那我们康康MainActivity_ViewBinding做了啥!
package com.llk.kt;
import android.view.View;
import android.widget.Button;
import androidx.annotation.CallSuper;
import androidx.annotation.UiThread;
import butterknife.Unbinder;
import butterknife.internal.DebouncingOnClickListener;
import butterknife.internal.Utils;
import java.lang.IllegalStateException;
import java.lang.Override;
public class MainActivity_ViewBinding implements Unbinder { //实现了Unbinder
private MainActivity target;
private View view7f070044;
private View view7f070045;
@UiThread
public MainActivity_ViewBinding(MainActivity target) {
this(target, target.getWindow().getDecorView());
}
//( ̄口 ̄)!! 原来是在*_ViewBinding类的构造方法里边帮我们findViewById、setOnClickListener的啊,真棒。
@UiThread
public MainActivity_ViewBinding(final MainActivity target, View source) {
this.target = target;
View view;
//Utils.findRequiredView执行了findViewById
view = Utils.findRequiredView(source, R.id.btn, "field 'btn' and method 'click'");
//Utils.castView执行了将view转换成该view的对应类型
//如果xml定义的View和Activity中通过注解绑定的View类型不一致,会主动抛出异常(IllegalStateException("View '" + name + "' with ID " + id + " for " + who + " was of the wrong type. See cause for more info.", e);)
target.btn = Utils.castView(view, R.id.btn, "field 'btn'", Button.class);
view7f070044 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.click(p0);
}
});
view = Utils.findRequiredView(source, R.id.btn2, "field 'btn2' and method 'click'");
target.btn2 = Utils.castView(view, R.id.btn2, "field 'btn2'", Button.class);
view7f070045 = view;
view.setOnClickListener(new DebouncingOnClickListener() {
@Override
public void doClick(View p0) {
target.click(p0);
}
});
}
//Unbinder接口的唯一方法,用来提供给外部,当界面销毁的时候给我们解绑view用的。
@Override
@CallSuper
public void unbind() {
MainActivity target = this.target;
if (target == null) throw new IllegalStateException("Bindings already cleared.");
this.target = null;
target.btn = null;
target.btn2 = null;
view7f070044.setOnClickListener(null);
view7f070044 = null;
view7f070045.setOnClickListener(null);
view7f070045 = null;
}
}
那我们用APT来造乞丐版*_ViewBinding类吧
ButterKnife注解处理的源码没看过,感觉会很多。
定义注解BindView、BindLayout、BindClick
@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.FIELD)
public @interface BindView {
int id() default -1;
}
@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.TYPE)
public @interface BindLayout {
int id() default -1;
}
@Retention(RetentionPolicy.SOURCE)
@Target(value = ElementType.METHOD)
public @interface BindClick {
int[] ids();
}
定义注入工具类
//注入的工具类
//通过反射获取到*_ViewBinding类,实例化之后就调它的bind方法
public class BindUtils {
public static <T> void bind(T activity){
try {
Class<?> clazz = Class.forName(activity.getClass().getCanonicalName() + "_ViewBinding");
Constructor constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
Method method = clazz.getDeclaredMethod("bind", activity.getClass());
method.invoke(obj, activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注解的使用
@BindLayout(id = R.layout.activity_main)
public class MainActivity extends Activity {
@BindView(id = R.id.btn)
public Button btn;
@BindView(id = R.id.btn2)
public Button btn2;
@BindClick(ids = {R.id.btn, R.id.btn2})
public void click(View view){
switch (view.getId()){
case R.id.btn:
Toast.makeText(this, "我按了1", Toast.LENGTH_SHORT).show();
break;
case R.id.btn2:
Toast.makeText(this, "我按了2", Toast.LENGTH_SHORT).show();
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BindUtils.bind(this);
btn.setText("我是1");
btn2.setText("我是2");
}
}
自定义注解处理器生成*_ViewBinding类
public class BindCompiler extends AbstractProcessor {
private static final String CLASS_TAIL = "_ViewBinding";
private Elements elementUtils;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
//添加我们要处理的注解
HashSet<String> set = new HashSet<>();
set.add(BindView.class.getCanonicalName());
set.add(BindLayout.class.getCanonicalName());
set.add(BindClick.class.getCanonicalName());
return set;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//Key为Activity类的Element,Value为该Activity类中所有的注解信息
Map<Element, List<BindElementInfo>> map = new HashMap<>();
//遍历所有相关注解的,缓存到map中
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)){
String fieldName = element.getSimpleName().toString();
int id = element.getAnnotation(BindView.class).id();
Element supperElement = element.getEnclosingElement();
BindElementInfo info = new BindElementInfo(BindView.class, fieldName, id);
if (map.containsKey(supperElement)){
map.get(supperElement).add(info);
}else {
List<BindElementInfo> list = new ArrayList<>();
list.add(info);
map.put(supperElement, list);
}
}
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindLayout.class)){
int id = element.getAnnotation(BindLayout.class).id();
BindElementInfo info = new BindElementInfo(BindLayout.class,
element.getSimpleName().toString(),
id);
if (map.containsKey(element)){
map.get(element).add(0, info);
}else {
List<BindElementInfo> list = new ArrayList<>();
list.add(info);
map.put(element, list);
}
}
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindClick.class)){
int[] ids = element.getAnnotation(BindClick.class).ids();
BindElementInfo info = new BindElementInfo(BindClick.class,
element.getSimpleName().toString(),
ids);
Element supperElement= element.getEnclosingElement();
if (map.containsKey(supperElement)){
map.get(supperElement).add(info);
}else {
List<BindElementInfo> list = new ArrayList<>();
list.add(info);
map.put(supperElement, list);
}
}
//遍历map,通过javapoet工具建造java类
for (Element element : map.keySet()){
//包名
String pkgName = elementUtils.getPackageOf(element).getQualifiedName().toString();
//方法
MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind") //方法名
.addModifiers(Modifier.PUBLIC) //修饰符
.returns(void.class) //返回值
.addParameter(TypeName.get(element.asType()), "activity"); //传参
//方法内添加代码
//必须先调用setContentView,你懂的。
List<BindElementInfo> bindElementInfos = map.get(element);
BindElementInfo firstInfo = bindElementInfos.get(0);
//setContentView(R.layout.activity_main);
if (BindLayout.class.equals(firstInfo.clazz)){
methodSpecBuilder.addStatement("activity.setContentView($L)", firstInfo.ids[0]);
}
for (BindElementInfo info : map.get(element)) {
//Button btn = findViewById(R.id.btn);
if (BindView.class.equals(info.clazz)) {
methodSpecBuilder.addStatement("activity.$L=activity.findViewById($L)",
info.name,
info.ids[0]);
}else if (BindClick.class.equals(info.clazz)){
//findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) { click(v); }
// });
for (int id : info.ids){
methodSpecBuilder.addStatement(
"activity.findViewById($L).setOnClickListener(new android.view.View.OnClickListener() {\n" +
" @Override\n" +
" public void onClick(android.view.View v) { activity.$L(v); }\n" +
" });", id, info.name);
}
}
}
MethodSpec methodSpec = methodSpecBuilder.build();
TypeSpec typeSpec = TypeSpec.classBuilder(element.getSimpleName() + CLASS_TAIL) //类名
.addModifiers(Modifier.PUBLIC) //修饰符
.addMethod(methodSpec) //方法
.build();
//通过包名和类生成一个java文件
JavaFile build = JavaFile.builder(pkgName, typeSpec).build();
try {
build.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
static class BindElementInfo{
int[] ids; //注解的值
String name; //元素的名称
Class<? extends Annotation> clazz; //注解类型
public BindElementInfo(Class<? extends Annotation> clazz, String name, int... ids) {
this.ids = ids;
this.name = name;
this.clazz = clazz;
}
@Override
public String toString() {
return "BindElementInfo{" +
" ids=" + Arrays.toString(ids) +
" name=" + name +
" clazz=" + clazz.toString() +
"}";
}
}
}
生成*_ViewBinding的样子
package com.llk.kt;
public class MainActivity_ViewBinding {
public void bind(MainActivity activity) {
activity.setContentView(2131361821);
activity.btn=activity.findViewById(2131165250);
activity.btn2=activity.findViewById(2131165251);
activity.findViewById(2131165250).setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View v) { activity.click(v); }
});;
activity.findViewById(2131165251).setOnClickListener(new android.view.View.OnClickListener() {
@Override
public void onClick(android.view.View v) { activity.click(v); }
});;
}
}
代码丢gayhub了 ButterKnife_AptDemo