APT 的使用

一、什么是 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 set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.NOTE, "没有人使用该注解: ");
            return false;
        }
        // 查找被 ARouter 注解注释过的类
        Set 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);

五、整个工程结构

APT 的使用_第1张图片
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下有一个标签

APT 的使用_第2张图片
image.png

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

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

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

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

annotationProcess project(':apt')

你可能感兴趣的:(APT 的使用)