Java注解处理器学习记录(实现乞丐版ButterKnife)

什么是注解处理器(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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值