Android 自定义编译时注解(APT)

APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器,APT可以用来在编译时扫描和处理注解,通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写,注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。

image.png

apt是在生成.class文件之前执行,故在apt里面无法通过反射获取其他类的方法,因为反射是通过ClassLoader将Class文件加载到JVM中,在内存中进行管理。

注解处理器是运行它自己的虚拟机JVM中,javac启动一个完整Java虚拟机来运行注解处理器,

自定义编译注解

工程结构:

  • annotation (注解和处理器生成代码相关,有的喜欢将注解和处理器分成两个包)
  • app

annotation 模块

  • 新建java lib 命名annotation
  • build.gradle 导入依赖
dependencies {
    implementation 'com.squareup:javapoet:1.13.0'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
  • 新建java类定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface NativeAnnotation {
    String path() default "";
}
  • 新建Processor
@AutoService(Processor.class)
public class NativeProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        return false;
    }
}

app模块

  • build.gradle 导入依赖
    implementation project(':annotation')
    annotationProcessor project(':annotation')
  • 使用注解
@NativeAnnotation(path = "111")
public class MainActivity extends AppCompatActivity {}

在处理器里面加入log 确认处理器有没有生效

@AutoService(Processor.class)
public class NativeProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING," test NativeProcessor");
        return false;
    }
}

注解解释器输出的日志在build log里面查看


捕获.PNG

从log中可以看出 解释器已生效, 在process方法中可以写自己想要的逻辑,比如生成java文件

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING," test NativeProcessor");
        ClassName className = ClassName.bestGuess("com.example.Useful");

        TypeSpec.Builder userTypeSpec = TypeSpec.classBuilder(className)

                .addModifiers(Modifier.PUBLIC);
        // private int id = 0;
        FieldSpec idFieldSpec = FieldSpec.builder(int.class, "id", Modifier.PRIVATE)
                .initializer("0").build();

        userTypeSpec.addField(idFieldSpec);
        userTypeSpec.addJavadoc("注释");
        JavaFile javaFile = JavaFile.builder("com.example",userTypeSpec.build()).build();
        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

在build目录下已生成对应文件


捕获.PNG

注解处理器核心类与函数解析

AbstractProcessor

AbstractProcessor抽象类是实现了Processor接口,具体类变量和函数解析如下:

  • init(ProcessingEnvironment env):init()方法会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。

  • process(Set<? extends TypeElement> annotations, RoundEnvironment env): 这相当于每个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。

  • getSupportedAnnotationTypes(): 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。

  • getSupportedSourceVersion(): 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。我推荐你使用前者。

ProcessingEnvironment

ProcessingEnvironment对象是apt的核心工具类


捕获.PNG

获取Elements的类型

processingEnv.getElementUtils().getTypeElement(type).asType()

输出调试日志

     processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING," 
test NativeProcessor");

判断此元素的类型,做相关安全校验之类的

private boolean isSubtype(Element typeElement, String type) {
  return processingEnv.getTypeUtils().isSubtype(typeElement.asType(),
      processingEnv.getElementUtils().getTypeElement(type).asType());
}

返回用来创建类或者辅助文件的filer

  JavaFile javaFile = JavaFile.builder("com.example",userTypeSpec.build()).build();
        try {
            javaFile.writeTo(processingEnv.getFiler());
        } catch (IOException e) {
            e.printStackTrace();
        }
RoundEnvironment
public interface RoundEnvironment {

    boolean processingOver();

     //上一轮注解处理器是否产生错误
    boolean errorRaised();

     //返回上一轮注解处理器生成的根元素
    Set<? extends Element> getRootElements();

   //返回包含指定注解类型的元素的集合
    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);

    //返回包含指定注解类型的元素的集合
    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
}
Element

element表示一个静态的,语言级别的构件。而任何一个结构化文档都可以看作是由不同的element组成的结构体,对java源文件来说

package com.closedevice;             //PackageElement

public class Main{                  //TypeElement
    private int x;                  //VariableElement

    private Main(){                 //ExecuteableElement

    }
    private void print( //ExecuteableElement
                              int msg
                             ){ //VariableElement
    }

}

Element代表程序元素:包,类,方法都是一种程序元素


捕获.PNG
  • VariableElement 代表一个 字段, 枚举常量, 方法或者构造方法的参数, 局部变量及 异常参数等元素
  • PackageElement 代表包元素
  • TypeElement 代表类或接口元素
  • ExecutableElement 代码方法,构造函数,类或接口的初始化代码块等元素,也包括注解类型元素
public interface Element extends javax.lang.model.AnnotatedConstruct {
  //返回一个TypeMirror元素的类型信息,包括包名,类(或方法,或参数)
  //名的类型,在生成动态代码的时候,我们往往需要知道变量/方法参数的类
  //型  ,以便写入正确的类型声明
    TypeMirror asType();
   //返回element的类型,判断是哪种element
    ElementKind getKind();
   //获取修饰关键字,入public static final,abstract等关键字
    Set<Modifier> getModifiers();
   //获取名字,不带包名
    Name getSimpleName();

   //getEnclosedElements
    Element getEnclosingElement();

    //返回该元素直接包含的子元素,通常对一个PackageElement而言,它可
   //以包含TypeElement;对于一个TypeElement而言,它可能包含属性
   //VariableElement,方法ExecutableElement
    List<? extends Element> getEnclosedElements();

    //获取该元素上的注解的类型信息
    @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();
   //获取该元素上的注解
    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);
    @Override
    <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType);
}

以上是Element的方法,同时它的子类有自己的方法

public interface ExecutableElement extends Element, Parameterizable {
//用于获取方法的参数元素,每个元素是一个VariableElement
List<? extends VariableElement> getParameters();
//获取方法元素的返回值,返回类型TypeMirror表示
TypeMirror getReturnType()

}
public interface VariableElement extends Element {
//如果属性变量被final修饰,则可以使用该方法获取它的值
Object getConstantValue();
}
public interface TypeElement extends Element, Parameterizable, QualifiedNameable {
    //获取类全限定名
   Name getQualifiedName();
}

ElementKind

public enum ElementKind {

    /** A package. */
    PACKAGE,

    // Declared types
    /** An enum class. */
    ENUM,
    /**
     * A class not described by a more specific kind (like {@code
     * ENUM} or {@code RECORD}).
     */
    CLASS,

    /** An annotation interface. (Formerly known as an annotation type.) */
    ANNOTATION_TYPE,
    /**
     * An interface not described by a more specific kind (like
     * {@code ANNOTATION_TYPE}).
     */
    INTERFACE,

    // Variables
    /** An enum constant. */
    ENUM_CONSTANT,
    /**
     * A field not described by a more specific kind (like
     * {@code ENUM_CONSTANT}).
     */
    FIELD,
    /** A parameter of a method or constructor. */
    PARAMETER,
    /** A local variable. */
    LOCAL_VARIABLE,
    /** A parameter of an exception handler. */
    EXCEPTION_PARAMETER,

    // Executables
    /** A method. */
    METHOD,
    /** A constructor. */
    CONSTRUCTOR,
    /** A static initializer. */
    STATIC_INIT,
    /** An instance initializer. */
    INSTANCE_INIT,

    /** A type parameter. */
    TYPE_PARAMETER,

    /**
     * An implementation-reserved element.  This is not the element
     * you are looking for.
     */
    OTHER,

    // Constants added since initial release

    /**
     * A resource variable.
     * @since 1.7
     */
     RESOURCE_VARIABLE,

    /**
     * A module.
     * @since 9
     */
     MODULE,

    /**
     * A record class.
     * @since 16
     */
    RECORD,

    /**
     * A record component of a {@code record}.
     * @since 16
     */
    RECORD_COMPONENT,

    /**
     * A binding variable in a pattern.
     * @since 16
     */
    BINDING_VARIABLE;
}
Modifier

public enum Modifier {
/** The modifier {@code public} */          PUBLIC,
    /** The modifier {@code protected} */       PROTECTED,
    /** The modifier {@code private} */         PRIVATE,
    /** The modifier {@code abstract} */        ABSTRACT,
    /**
     * The modifier {@code default}
     * @since 1.8
     */
     DEFAULT,
    /** The modifier {@code static} */          STATIC,

    /**
     * The modifier {@code sealed}
     * @since 17
     */
    SEALED,

    /**
     * The modifier {@code non-sealed}
     * @since 17
     */
    NON_SEALED {
        public String toString() {
            return "non-sealed";
        }
    },
    /** The modifier {@code final} */           FINAL,
    /** The modifier {@code transient} */       TRANSIENT,
    /** The modifier {@code volatile} */        VOLATILE,
    /** The modifier {@code synchronized} */    SYNCHRONIZED,
    /** The modifier {@code native} */          NATIVE,
    /** The modifier {@code strictfp} */        STRICTFP;
}
TypeMirror接口
public interface TypeMirror extends javax.lang.model.AnnotatedConstruct {
   //返回TypeKind类型,java语言中的类型.Types包括基本类型,声明类型(类类型和接口类 
   //型),数组,类型变量和空类型
 TypeKind getKind();
   @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();
    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);
    @Override
    <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationType);
}
public enum TypeKind {
    /**
     * The primitive type {@code boolean}.
     */
    BOOLEAN,

    /**
     * The primitive type {@code byte}.
     */
    BYTE,

    /**
     * The primitive type {@code short}.
     */
    SHORT,

    /**
     * The primitive type {@code int}.
     */
    INT,

    /**
     * The primitive type {@code long}.
     */
    LONG,

    /**
     * The primitive type {@code char}.
     */
    CHAR,

    /**
     * The primitive type {@code float}.
     */
    FLOAT,

    /**
     * The primitive type {@code double}.
     */
    DOUBLE,

    /**
     * The pseudo-type corresponding to the keyword {@code void}.
     * @see NoType
     */
    VOID,

    /**
     * A pseudo-type used where no actual type is appropriate.
     * @see NoType
     */
    NONE,

    /**
     * The null type.
     */
    NULL,

    /**
     * An array type.
     */
    ARRAY,

    /**
     * A class or interface type.
     */
    DECLARED,

    /**
     * A class or interface type that could not be resolved.
     */
    ERROR,

    /**
     * A type variable.
     */
    TYPEVAR,

    /**
     * A wildcard type argument.
     */
    WILDCARD,

    /**
     * A pseudo-type corresponding to a package element.
     * @see NoType
     */
    PACKAGE,

    /**
     * A method, constructor, or initializer.
     */
    EXECUTABLE,

    /**
     * An implementation-reserved type.
     * This is not the type you are looking for.
     */
    OTHER,

    /**
      * A union type.
      *
      * @since 1.7
      */
    UNION,

    /**
      * An intersection type.
      *
      * @since 1.8
      */
    INTERSECTION,

    /**
     * A pseudo-type corresponding to a module element.
     * @see NoType
     * @since 9
     */
    MODULE
}

代码示例

获取一个类注解的值,并且获取类里面的方法
  • 使用注解
@NativeAnnotation(path = " path hahaha")
public class test {
    public native int nativeInit(Fragment i, int j, String[] strings, 
ArrayList arrayList);
}
  • process书写逻辑
 public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //找出含有NativeAnnotation注解元素
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(NativeAnnotation.class);
        for (Element element : elements) {
            //获取element元素上的注解
            NativeAnnotation aah = element.getAnnotation(NativeAnnotation.class);
            //获取注解的值
            String path = aah.path();
            processingEnv.getMessager().printMessage(
                    Diagnostic.Kind.WARNING," value path : " +  path);
            //判断是否是类注解
            if (element.getKind() == ElementKind.CLASS) {
                TypeElement typeElement = (TypeElement) element;
                //获取全类名
                String className = typeElement.getQualifiedName().toString();
                processingEnv.getMessager().printMessage(
                        Diagnostic.Kind.WARNING," className: " +  className);
                //获取类里面的元素
                List<? extends Element> elements1 = typeElement.getEnclosedElements();
                for (Element element1 : elements1) {
                    //判断元素是否是方法
                    if (element1.getKind() == ElementKind.METHOD) {
                        ExecutableElement executableElement = (ExecutableElement)element1;
                        //打印方法名
                        processingEnv.getMessager().printMessage
                                (Diagnostic.Kind.WARNING," method : " 
                                        +  executableElement.getSimpleName());
                        //打印方法返回值
                        processingEnv.getMessager().printMessage
                                (Diagnostic.Kind.WARNING," return : " 
                                        +  executableElement.getReturnType().toString());
                        //打印方法修饰符
                        processingEnv.getMessager().printMessage
                                (Diagnostic.Kind.WARNING," Modifiers : " 
                                        +  executableElement.getModifiers().toString());
                        //获取方法参数
                        List<? extends VariableElement> variableElements = executableElement.getParameters();
                        for (VariableElement element2 : variableElements) {
                            //打印参数名称
                            processingEnv.getMessager().printMessage
                                    (Diagnostic.Kind.WARNING," Parame name: "
                                            +  element2.getSimpleName());
                            //打印参数类型
                            processingEnv.getMessager().printMessage
                                    (Diagnostic.Kind.WARNING," Parame TypeKind : " 
                                            +  element2.asType().getKind().name());
                            //打印参数类型
                            processingEnv.getMessager().printMessage
                                    (Diagnostic.Kind.WARNING," Parame type : " 
                                            +  element2.asType().toString());
                        }
                    }

                }


            }

        }
}
  • 运行结果
value path :  path hahaha
����:  className: com.example.annotationjnicheck.test
����:  method : nativeInit
����:  return : int
����:  Modifiers : [public, native]
����:  Parame name: i
����:  Parame TypeKind : DECLARED
����:  Parame type : android.app.Fragment
����:  Parame name: j
����:  Parame TypeKind : INT
����:  Parame type : int
����:  Parame name: strings
����:  Parame TypeKind : ARRAY
����:  Parame type : java.lang.String[]
����:  Parame name: arrayList
����:  Parame TypeKind : DECLARED
����:  Parame type : java.util.ArrayList

参考链接:
https://blog.csdn.net/heng615975867/article/details/105072317/
http://www.360doc.com/showweb/0/0/1036633837.aspx

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容