美文网首页
带你了解APT

带你了解APT

作者: 歇斯底里的苦笑 | 来源:发表于2021-02-23 16:35 被阅读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);
    }
    

    相关文章

      网友评论

          本文标题:带你了解APT

          本文链接:https://www.haomeiwen.com/subject/aguttltx.html