带你了解APT

2021-02-23  本文已影响0人  歇斯底里的苦笑

介绍

APT就是(Annotation Processing Tool )的简称,就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。

代表框架:
Dagger2
ButterKnife
EventBus
ARouter

作用

使用APT的优点就是方便、简单,可以少些很多重复的代码。


APT处理要素

注册处理器(AutoService) + 注解处理器(AbstractProcessor) + 代码生成(javapoet)

1、 注册处理器

注册处理器有2种方式
一种是AutoService
首先引入

    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc2'

然后再AbstractProcessor类中加上注解@AutoService(Processor.class)
这样就注册完成了
第二种是添加文件

image.png
2、 注解处理器(AbstractProcessor)

创建一个继承AbstractProcessor 类就可以了,在process方法里,获取我们需要的哪些注解。

public class BindViewProcessor extends AbstractProcessor {

public class BindViewProcessor extends AbstractProcessor {
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    /**
     * 指定注解
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(BindView.class.getCanonicalName());
        return supportTypes;
    }

    /**
     * 用来指定你使用的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 扫描注解处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return true;
    }
3、 代码生成

代码生成也有2种方式来实现
1.StringBuilder来实现,大概例子是这样的,所有的代码都是用字符串拼接起来,生成的代码格式也很乱,而且很容易写错。
2.另外一种是使用javapoet生成代码,生成的代码会自动排版,javapoet详细用法

可以看下两者的比较。

   /**
     * 使用StringBuilder创建类、而且还需要自己手写导入包,这边没写
     */
    public String generateJavaCode() {
        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(mPackageName).append(";\n\n");
        builder.append('\n');
        builder.append("public class ").append(mBindingClassName);
        builder.append(" {\n");

        generateMethods(builder);//创建方法
        builder.append('\n');
        builder.append("}\n");
        return builder.toString();
    }

    /**
     * 使用javapoet创建类
     */
    public TypeSpec generateJavaCode2() {
        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethods2())//创建方法
                .build();
        return bindingClass;

    }

    /**
     * 使用StringBuilder创建方法
     */
    private void generateMethods(StringBuilder builder) {
        builder.append("public void bind(" + mTypeElement.getQualifiedName() + " target ) {\n");
        for (int id : mapId.keySet()) {
            VariableElement element = mapId.get(id);
            String name = element.getSimpleName().toString();
            builder.append("target." + name).append(" = ");
            builder.append("target.findViewById( " + id + ");\n");
        }
        builder.append("  }\n");
    }
  /**
     * 使用javapoet创建方法
     */
    private MethodSpec generateMethods2() {
        ClassName target = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(target, "target");

        for (int id : mapId.keySet()) {
            VariableElement element = mapId.get(id);
            String name = element.getSimpleName().toString();
            methodBuilder.addCode("target." + name + " = " + "target.findViewById( " + id + ");");
        }
        return methodBuilder.build();
    }

这三步完成我们就可以生成我们要的代码了。
javapoet详细用法


获取注解对象

1、运行时注解: 通过 反射 机制获取注解对象,会损耗性能,常用的框架有retrofit
2、编译期注解: 通过 APT 方式获取注解对象,不会造成性能损耗。常用的框架Dagger2, ButterKnifeEventBusARouter

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
}

@Retention(RetentionPolicy.CLASS):表示编译时注解
@Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)

注解 @Target代表意思
@Target(ElementType.TYPE)                 接口、类、枚举、注解
@Target(ElementType.FIELD)                字段、枚举的常量
@Target(ElementType.METHOD)               方法
@Target(ElementType.PARAMETER)            方法参数
@Target(ElementType.CONSTRUCTOR)          构造函数
@Target(ElementType.LOCAL_VARIABLE)       局部变量
@Target(ElementType.ANNOTATION_TYPE)      注解
@Target(ElementType.PACKAGE)              包

详细内容

element 的概念

表示一个程序元素,比如包、类或者方法。
element 包含有
PackageElement 表示一个包程序元素
TypeElement 表示一个类或接口程序元素
VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数
如下图


public class Foo {        // TypeElement 类型元素

    private int a;      // VariableElement 变量元素
    private Foo other;  // VariableElement 变量元素

    public Foo() { // ExecutableElement 可执行元素
    }

    public void setA(int newA ) { //   newA  代表是一个 VariableElement)
    }
}

Element 的源码,源码解析

public interface Element extends javax.lang.model.AnnotatedConstruct {
    /**
     * 返回该元素定义的类型。
     * 泛型元素定义了一系列类型,而不仅仅是一个类型。如果这是一个泛型元素,则返回一个原型
     * 类型。这是元素在对应于它自己的正式类型参数的类型变量上的调用。例如,对于泛型类元素
     * C<N extends Number>,返回参数化类型C<N>。类型实用程序接口有更一般的方法来获取元
     * 素定义的所有类型的范围。 
     */
    TypeMirror asType();

    /**
     * 返回该元素的类型
     */
    ElementKind getKind();

    /**
     * 返回该元素的修饰符,包括注解.
     * 隐式修饰符也包含,比如接口方法中的public和static
     */
    Set<Modifier> getModifiers();

    /**
     * 返回该元素的简单名称.泛型类型的名称不包括对其正式类型参数的任何引用。
     * 举例,java.util.Set<E>的简单名称是Set.
     * 如果该元素代表的是未命名包,则返回一个空 Name.
     * 如果代表的是构造器,则返回<init>所对应的Name.如果代表的是静态代码块,则返回的是<clinit>
     * 如果代表的是匿名类或者是初始代码块,则返回一个空 Name.
     */
    Name getSimpleName();

    /**
     * 返回包围该元素的最内层的元素.
     * 如果这个元素的声明紧接在另一个元素的声明中,则返回另一个元素。
     * 如果这是顶级类型,则返回其包。
     * 如果这是一个包,则返回null。
     * 如果这是类型参数,则返回类型参数的泛型元素。
     * 如果这是一个方法或构造函数参数,则返回声明该参数的可执行元素。
     */
    Element getEnclosingElement();

    /**
     * 返回该元素所包含的元素.
     * 类或接口被认为包含了它直接声明的字段、方法、构造函数和成员类型.包直接包含了顶级类和接
     * 口,但不包含其子包。其他类型的元素目前不被认为包含任何元素;然而,随着这个API或编程语
     * 言的发展,这些元素可能会改变
     */
    List<? extends Element> getEnclosedElements();

    /**
     * 当给定的参数和当前类代表同一个元素时返回true,否则,返回false.
     * 注意,元素的标识涉及不能直接从元素的方法中访问的隐式状态,包括关于不相关类型的存在的
     * 状态。即使“同一个”元素正在被建模,由这些接口的不同实现创建的元素对象也不应该期望相
     * 等;这类似于通过不同的类加载器加载的同一个类文件的Class对象是不同的
     *
     */
    @Override
    boolean equals(Object obj);

    /**
     * 基于Object.hashCode
     */
    @Override
    int hashCode();


    /**
     * 获得直接声明在该元素上的注解
     * 如果要获得继承的注解,使用Elements#getAllAnnotationMirrors(Element)方法.
     */
    @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();


    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);


    <R, P> R accept(ElementVisitor<R, P> v, P p);
}
上一篇下一篇

猜你喜欢

热点阅读