APT

作者: radish520like | 来源:发表于2018-05-22 17:38 被阅读0次

    APT 简介

      APT 就是注解处理器,他是 javac 的一个工具,用来在编译时扫描和处理注解。一个注解处理器它以 Java 代码作为输入,生成文件(通常是 java 文件),这些生成的 java 文件不能修改,并且会和我们手动编写的 java 文件一样会被 javac 进行编译。
      这里我们简单模拟一下 ButterKnife 的原理,来讲解 APT.

    简单使用

      首先我们新建一个项目,在项目中新建一个 Java Module,命名为 annotation,用于声明注解,然后在 annotation Module 中新建一个元注解类命名为 BindView

    package com.radish.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    //作用在属性上
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface BindView {
    
        int value();
    }
    

      然后我们再新建一个 Java Module 命名为 compiler(注意不是 Android Librarry,是 Java Library)

    image.png
      然后在 compiler Moodule 中新建一个类,命名为 TestProcessor 并且 extends AbstractProcessor。
      首先,我们需要将我们自定义的注解处理器进行注册,在 build.gradle 中添加配置,然后修改代码
    compile 'com.google.auto.service:auto-service:1.0-rc2'
    package com.radish.compiler;
    
    import com.google.auto.service.AutoService;
    
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Processor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.lang.model.element.TypeElement;
    
    
    //自动注册
    @AutoService(Processor.class)
    
    public class TestProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            return false;
        }
    }
    

      接下来,我们需要指定一些配置,比如说,该注解处理器可以处理什么样的注解,Java 版本是多少,初始化一些工具类等等。

    package com.radish.compiler;
    
    import com.google.auto.service.AutoService;
    
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Messager;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.Processor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.annotation.processing.SupportedSourceVersion;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.TypeElement;
    
    
    //自动注册
    @AutoService(Processor.class)
    //可以处理的注解,必须是全类名
    @SupportedAnnotationTypes({"com.radish.annotation.BindView"})
    //编译时候的 Java 版本
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public class TestProcessor extends AbstractProcessor {
    
       /**
         * 日志工具
         */
        private Messager messager;
        /**
         * 文件工具
         */
        private Filer filer;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            messager = processingEnv.getMessager();
            filer = processingEnv.getFiler();
        }
    
        /**
         *
         * @param annotations   使用了当前注解处理器允许处理的注解的节点集合
         *                      那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
         * @param roundEnv      环境
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
            return false;
        }
    }
    

      接下来我们就需要了解最主要的一个方法 process,该法方法是一定会被调用的方法,就类似于 Java 中的 main 方法

        /**
         *
         * @param annotations   使用了当前注解处理器允许处理的注解的节点集合
         *                      那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
         * @param roundEnv      环境
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            for(TypeElement typeElement : annotations){
                Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
                for(Element element : elements){
                    messager.printMessage(Diagnostic.Kind.NOTE,element.getSimpleName());
                }
            }
            return false;
        }
    

      然后,我们在 app Module 中引入 annotation Module:

    //是 apt 中的一种工具,是 google 开发的内置框架,不需要引入,直接在 build.gralde 文件中引入
    annotationProcessor project(':compiler')
    compile project(':annotation')
    

    这样就可以开始使用注解了

    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.tv)
        TextView tv;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

      然后我们 make 一下 app


    image.png

      目前我们 process 中拿到的被注解的节点是 tv

        /**
         *
         * @param annotations   使用了当前注解处理器允许处理的注解的节点集合
         *                      那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
         * @param roundEnv      环境
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            for(TypeElement typeElement : annotations){
                Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
                for(Element element : elements){
    //                messager.printMessage(Diagnostic.Kind.NOTE,element.getSimpleName());
                    //获取其父节点(tv 的父节点:MainActivity)
                    Element enclosingElement = element.getEnclosingElement();
                    /*
                        假如我们需要创建一个类,假如叫 MainActivity_Binding
                        enclosingElement.getSimpleName().toString() + "_Binding";
                        但是类里面的代码,都需要字符串的拼接,比较繁琐而且容易出错,
                        那么我们就可以借助 javapoet 来进行 java 文件的创建
                     */
                }
            }
            return false;
        }
    

    JavaPoet

      在 build.gradle 添加一行配置compile 'com.squareup:javapoet:1.7.0',其github 的网址是:

    https://github.com/square/javapoet

        /**
         *
         * @param annotations   使用了当前注解处理器允许处理的注解的节点集合
         *                      那些地方使用了我们可以处理的注解,被注解的节点就会放到 Set 集合中
         * @param roundEnv      环境
         * @return
         */
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            for(TypeElement typeElement : annotations){
                Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(typeElement);
                for(Element element : elements){
                    //获取其父节点(tv 的父节点:MainActivity)
                    Element enclosingElement = element.getEnclosingElement();
                    String className = enclosingElement.getSimpleName() + "_Binding";
                    MethodSpec main = MethodSpec.methodBuilder("main")
                            .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                            .returns(void.class)
                            .addParameter(String[].class, "args")
                            .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                            .build();
    
                    TypeSpec helloWorld = TypeSpec.classBuilder(className)
                            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                            .addMethod(main)
                            .build();
    
                    JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
                            .build();
    
                    try {
                        javaFile.writeTo(filer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return true;
        }
    

      然后我们 make app,java 文件就生成了。


    image.png
    package com.example.helloworld;
    
    import java.lang.String;
    import java.lang.System;
    
    public final class MainActivity_Binding {
      public static void main(String[] args) {
        System.out.println("Hello, JavaPoet!");
      }
    }
    

      然后我们就可以在我们的类中去使用这个生成的 java 类了。


    image.png

    如何调试注解处理器

    image.png
    image.png
    image.png
    image.png
    image.png
    image.png
    image.png

      然后就可以开始 debug 调试模式了

    相关文章

      网友评论

          本文标题:APT

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