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

推荐阅读更多精彩内容