带你了解APT

介绍

APT就是(Annotation Processing Tool )的简称,就是可以在代码编译期间对注解进行处理,并且生成Java文件,减少手动的代码输入。

代表框架:
Dagger2
ButterKnife
EventBus
ARouter

作用

使用APT的优点就是方便、简单,可以少些很多重复的代码。


APT处理要素

注册处理器(AutoService) + 注解处理器(AbstractProcessor) + 代码生成(javapoet)

1、 注册处理器

注册处理器有2种方式
一种是AutoService
首先引入

    implementation 'com.google.auto.service:auto-service:1.0-rc2'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc2'

然后再AbstractProcessor类中加上注解@AutoService(Processor.class)
这样就注册完成了
第二种是添加文件

image.png

2、 注解处理器(AbstractProcessor)

创建一个继承AbstractProcessor 类就可以了,在process方法里,获取我们需要的哪些注解。

public class BindViewProcessor extends AbstractProcessor {

public class BindViewProcessor extends AbstractProcessor {
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    /**
     * 指定注解
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(BindView.class.getCanonicalName());
        return supportTypes;
    }

    /**
     * 用来指定你使用的Java版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /**
     * 扫描注解处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        return true;
    }
3、 代码生成

代码生成也有2种方式来实现
1.StringBuilder来实现,大概例子是这样的,所有的代码都是用字符串拼接起来,生成的代码格式也很乱,而且很容易写错。
2.另外一种是使用javapoet生成代码,生成的代码会自动排版,javapoet详细用法

可以看下两者的比较。

   /**
     * 使用StringBuilder创建类、而且还需要自己手写导入包,这边没写
     */
    public String generateJavaCode() {
        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(mPackageName).append(";\n\n");
        builder.append('\n');
        builder.append("public class ").append(mBindingClassName);
        builder.append(" {\n");

        generateMethods(builder);//创建方法
        builder.append('\n');
        builder.append("}\n");
        return builder.toString();
    }

    /**
     * 使用javapoet创建类
     */
    public TypeSpec generateJavaCode2() {
        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethods2())//创建方法
                .build();
        return bindingClass;

    }

    /**
     * 使用StringBuilder创建方法
     */
    private void generateMethods(StringBuilder builder) {
        builder.append("public void bind(" + mTypeElement.getQualifiedName() + " target ) {\n");
        for (int id : mapId.keySet()) {
            VariableElement element = mapId.get(id);
            String name = element.getSimpleName().toString();
            builder.append("target." + name).append(" = ");
            builder.append("target.findViewById( " + id + ");\n");
        }
        builder.append("  }\n");
    }
  /**
     * 使用javapoet创建方法
     */
    private MethodSpec generateMethods2() {
        ClassName target = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(target, "target");

        for (int id : mapId.keySet()) {
            VariableElement element = mapId.get(id);
            String name = element.getSimpleName().toString();
            methodBuilder.addCode("target." + name + " = " + "target.findViewById( " + id + ");");
        }
        return methodBuilder.build();
    }

这三步完成我们就可以生成我们要的代码了。
javapoet详细用法


获取注解对象

1、运行时注解: 通过 反射 机制获取注解对象,会损耗性能,常用的框架有retrofit
2、编译期注解: 通过 APT 方式获取注解对象,不会造成性能损耗。常用的框架Dagger2, ButterKnifeEventBusARouter

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
}

@Retention(RetentionPolicy.CLASS):表示编译时注解
@Target(ElementType.FIELD):表示注解范围为类成员(构造方法、方法、成员变量)

注解 @Target代表意思
@Target(ElementType.TYPE)                 接口、类、枚举、注解
@Target(ElementType.FIELD)                字段、枚举的常量
@Target(ElementType.METHOD)               方法
@Target(ElementType.PARAMETER)            方法参数
@Target(ElementType.CONSTRUCTOR)          构造函数
@Target(ElementType.LOCAL_VARIABLE)       局部变量
@Target(ElementType.ANNOTATION_TYPE)      注解
@Target(ElementType.PACKAGE)              包

详细内容

element 的概念

表示一个程序元素,比如包、类或者方法。
element 包含有
PackageElement 表示一个包程序元素
TypeElement 表示一个类或接口程序元素
VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
TypeParameterElement 表示一般类、接口、方法或构造方法元素的泛型参数
如下图


public class Foo {        // TypeElement 类型元素

    private int a;      // VariableElement 变量元素
    private Foo other;  // VariableElement 变量元素

    public Foo() { // ExecutableElement 可执行元素
    }

    public void setA(int newA ) { //   newA  代表是一个 VariableElement)
    }
}

Element 的源码,源码解析

public interface Element extends javax.lang.model.AnnotatedConstruct {
    /**
     * 返回该元素定义的类型。
     * 泛型元素定义了一系列类型,而不仅仅是一个类型。如果这是一个泛型元素,则返回一个原型
     * 类型。这是元素在对应于它自己的正式类型参数的类型变量上的调用。例如,对于泛型类元素
     * C<N extends Number>,返回参数化类型C<N>。类型实用程序接口有更一般的方法来获取元
     * 素定义的所有类型的范围。 
     */
    TypeMirror asType();

    /**
     * 返回该元素的类型
     */
    ElementKind getKind();

    /**
     * 返回该元素的修饰符,包括注解.
     * 隐式修饰符也包含,比如接口方法中的public和static
     */
    Set<Modifier> getModifiers();

    /**
     * 返回该元素的简单名称.泛型类型的名称不包括对其正式类型参数的任何引用。
     * 举例,java.util.Set<E>的简单名称是Set.
     * 如果该元素代表的是未命名包,则返回一个空 Name.
     * 如果代表的是构造器,则返回<init>所对应的Name.如果代表的是静态代码块,则返回的是<clinit>
     * 如果代表的是匿名类或者是初始代码块,则返回一个空 Name.
     */
    Name getSimpleName();

    /**
     * 返回包围该元素的最内层的元素.
     * 如果这个元素的声明紧接在另一个元素的声明中,则返回另一个元素。
     * 如果这是顶级类型,则返回其包。
     * 如果这是一个包,则返回null。
     * 如果这是类型参数,则返回类型参数的泛型元素。
     * 如果这是一个方法或构造函数参数,则返回声明该参数的可执行元素。
     */
    Element getEnclosingElement();

    /**
     * 返回该元素所包含的元素.
     * 类或接口被认为包含了它直接声明的字段、方法、构造函数和成员类型.包直接包含了顶级类和接
     * 口,但不包含其子包。其他类型的元素目前不被认为包含任何元素;然而,随着这个API或编程语
     * 言的发展,这些元素可能会改变
     */
    List<? extends Element> getEnclosedElements();

    /**
     * 当给定的参数和当前类代表同一个元素时返回true,否则,返回false.
     * 注意,元素的标识涉及不能直接从元素的方法中访问的隐式状态,包括关于不相关类型的存在的
     * 状态。即使“同一个”元素正在被建模,由这些接口的不同实现创建的元素对象也不应该期望相
     * 等;这类似于通过不同的类加载器加载的同一个类文件的Class对象是不同的
     *
     */
    @Override
    boolean equals(Object obj);

    /**
     * 基于Object.hashCode
     */
    @Override
    int hashCode();


    /**
     * 获得直接声明在该元素上的注解
     * 如果要获得继承的注解,使用Elements#getAllAnnotationMirrors(Element)方法.
     */
    @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();


    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);


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

推荐阅读更多精彩内容