美文网首页
APT 的使用

APT 的使用

作者: 石器时代小古董 | 来源:发表于2019-07-17 21:16 被阅读0次

    一、什么是 APT

    APT 的全称是 Annotation Processing Tool 注解处理器。可以将注解在编译期翻译成 Java代码

    二、如何使用 APT

    1.创建注解库

    新建一个 module 放置注解 比如 annotation module,里面还有 ARouter 注解

    1. 注意一定要是一个 java module
    1. 将注解单独放在一个库中,因为这个库不仅要被 APP 库引用,还需要被注解处理器库使用。
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    public @interface ARouter{
        String path();
    }
    

    三、创建一个注解处理器库

    1、创建一个专门用来处理注解的 Java Module。假设叫做 APT。在这个库的 build.gradle 下引入

    // 这是 google 提供的注解处理器库
    compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service1.0-rc4'
    // 这是 square 公司提供的生成 Java 源代码的库
    implementation 'com.squareup:javapoet:1.9.0'
    // 这里引入我们自己的注解库
    implementation project(':annotation')
    // 解决中文乱码问题
    task.withType(JavaCompile){
        options.encoding = "UTF-8"
    }
    

    2、创建一个注解处理器类继承 google 提供的 AbstractProcessor 类,并且在类上添加一些注解

    1.@AutoService 声明这个类是一个注解处理器
    2.@SupportedAnnotationTypes 表示关注的注解类
    3.SupportedSourceVersion 表示生成的 Java 源代码的版本
    4.@SupportedOptions 表示在编译时关注的 options,可以在编译时传递参数

    参数 作用
    Elements 查询元素节点的信息的工具
    Messager 日志工具类,在编译时打印日志
    Filer 文件生成器,生成 Java 代码时需要被用到
    @AutoService(Processor.class)
    @SupportedAnnotationTypes({"com.baidu.crazyorange.annotation.ARouter"})
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    @SupportedOptions("content")
    public class ARouterProcessor extends AbstractProcessor {
        // 元素节点
        private Elements elementUtils;
        private Types typesUtils;
        // 输出日志
        private Messager messager;
        // 文件生成器
        private Filer filer;
    
        /**
         * 初始化
         *
         * @param processingEnvironment
         */
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            // 初始化信息
            super.init(processingEnvironment);
            elementUtils = processingEnvironment.getElementUtils();
            typesUtils = processingEnvironment.getTypeUtils();
            messager = processingEnvironment.getMessager();
            filer = processingEnvironment.getFiler();
            String content = processingEnvironment.getOptions().get("content");
            messager.printMessage(Diagnostic.Kind.NOTE, "从app module 中获取到的参数: " + content);
        }
    .....
    
    

    在引入编译插件的地方加入javaCompileOptions,可以在编译时给注解解释器传入参数
    注意必须在 defaultConfig 代码块下面

      defaultConfig {
            applicationId appId.appId
            minSdkVersion androidCompile.minSdkVersion
            targetSdkVersion androidCompile.targetSdkVersion
            versionCode androidCompile.versionCode
            versionName androidCompile.versionName
            /**
             * 编译时给 APT 传入一些参数
             * 必须在defaultConfig节点下加入,否则会报错
             */
            javaCompileOptions{
                annotationProcessorOptions{
                    arguments = [content : 'hello apt']
                }
            }
        }
    

    process 函数式真正处理注解的地方,参数 set 返回使用了我们在 SupportedAnnotationTypes 注解的类。

    注意如果没有类使用我们关注的注解,Process 方法将不被执行

    1. 通过 Elements(ElementsUtils) 获取到使用注解类的包名

    String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();

    1. 通过 Element 获取到类名

    String className = element.getSimpleName().toString();

      @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            if (set.isEmpty()) {
                messager.printMessage(Diagnostic.Kind.NOTE, "没有人使用该注解: ");
                return false;
            }
            // 查找被 ARouter 注解注释过的类
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
            for (Element element : elements) {
                // 获取这个类节点的包节点信息 getQualifiedName 是全路径
                String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
                String className = element.getSimpleName().toString();
                messager.printMessage(Diagnostic.Kind.NOTE, "被被注解的包是: " + packageName);
                messager.printMessage(Diagnostic.Kind.NOTE, "被被注解的类是: " + className);
                // 最终我们要根据注解生成的类
                String finalClassName = className + "$$ARouter";
                .....
    }            
    

    四、使用 JavaPoet 翻译成 Java 源代码

    参数 作用
    MethodSpec 构造一个方法体
    FieldSpec 代表构造一个字段
    JavaFile 文件生成器,生成 Java 代码时需要被用到
    ParamterSpec 创建参数
    AnnotationSpec 创建注解
    ClassName 通过Element拿到拿到类信息
       /**
        * 使用 javaPort 来生成类文件
        */
        ARouter aRouter = element.getAnnotation(ARouter.class);
        // 生成方法体
        MethodSpec methodSpec = MethodSpec.methodBuilder("findTargetPath")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
        .addParameter(String.class, "path")
        // 构建方法体
        .addStatement("return path.equals($S)? $T.class:null", aRouter.path(), ClassName.get((TypeElement) element))
                            .returns(Class.class)
                            .build();
        // 构建类体                    
        TypeSpec typeSpec = TypeSpec.classBuilder(finalClassName).addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(methodSpec).build();
        // 将类写入具体的包下面
        JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
        javaFile.writeTo(filer);
    

    五、整个工程结构

    image.png

    六、生成类的样子

    package com.baidu.crazyorange.componenttest;
    
    import java.lang.Class;
    import java.lang.String;
    
    public final class MainActivity$$ARouter {
      public static Class findTargetPath(String path) {
        return path.equals("/app/MainActivity")? MainActivity.class:null;
      }
    }
    
    六、注意点

    1.确保 AbstractProcess 注册成功,如果注册成功可以在 APT 的 build--classes--java--main--META-INF下有一个标签

    image.png

    2.确保自己关注的注解被其他类使用,如果没被使用,process 方法将不执行

    3.通过在 init 方法中追加日志的方式,确保 init 正常

    4.确保关注的注解路径是正确的

    5.确保android module 引用了你的 apt module

    annotationProcess project(':apt')
    

    相关文章

      网友评论

          本文标题:APT 的使用

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