Android APT

1.APT注解处理器

APT是Annotation Processing Tool 的简称,即注解处理器。它是一种处理注解的工具,可以在代码编译期扫描并解析注解,最终生成处理注解逻辑的Java文件,从而减少手动的代码输入。ButterKnife、EventBus、ARouter、GreenDAO都使用了APT技术。

使用APT可以在编译时来处理编译时注解,生成额外的Java文件,有如下效果:

①可以达到减少重复代码手工编写的效果。如ButterKnife,可以直接使用注解来减少findviewbyid这些代码,只需要通过注解表示是哪个id就够了。

②功能封装。将主要的功能逻辑封装起来,只保留注解调用。

③相对于使用Java反射来处理运行时注解,使用APT有着更加良好的性能。

APT的作用时间是在编译开始时。首先要了解Android中代码编译流程:Java—>class —> dex,代码最终生成dex文件打入到APK包里面。

0aaba6fca9904278b5a12f6e55552af8.png

①APT是在编译开始时就介入的,用来处理编译时注解。

②AOP(Aspect Oridnted Programming)是在编译完成后生成dex文件之前,通过直接修改.class文件的方式,来对代码进行修改或添加逻辑。常用在在代码监控,代码修改,代码分析这些场景。

2.APT用法

用APT实现一个简单的功能:只要在任何类的成员变量上添加一个@Print注解,就可以动态生成一个方法,然后把成员变量的变量名输出:

b9e84e4cd5774b199d098b9489553639.png

 动态生成的类大概长这样:

754382d4256c454ca5b7dde6c0a874ad.png

 APT使用的流程如下:

①首先需要创建两个JavaLibrary,一个用来创建自定义注解;一个用来创建注解处理器,扫描注解;

②获取到添加注解的成员变量名,处理java文件生成逻辑;

③动态生成类和方法,用IO生成文件。

1)首先配置APT:

①用Android Studio创建一个空项目,然后创建两个JavaLibrary,一个注解的Lib: apt-annotation,一个扫描注解的Lib: apt-processor。

3966224af0ad44b8bfe8b00b1ddaa7da.png

b4f90b65e952446badb31cd20a92819e.png 

创建完之后:

a94ba8356b854affb0a5d6efddd28e95.png

②app模块依赖两个Library

implementation project(path: ':apt-annotation')

annotationProcessor project(path: ':apt-processor')

9012dc159a054d08b9b459e4a45c48a1.png

 ③在注解Lib中创建一个注解类

@Retention(RetentionPolicy.CLASS)

@Target(ElementType.FIELD)

public @interface Print {

}

9d82c8136d4a436a869f4a0d882737f9.png

 ④注解处理器的注册

自定义的注解处理器必须经过注册才能够使用,可以使用Google autoService来进行注解处理器的自动注册。首先需要在注解处理器apt-processor所在的module的build.gradle文件添加autoService的包引入,即扫描注解的Lib添加依赖

dependencies {

    //自动注册,动态生成 META-INF/...文件

    implementation 'com.google.auto.service:auto-service:1.0-rc6'

    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'

    //依赖apt-annotation

    implementation project(path: ':apt-annotation')

}

5c41e8a1662d49a1aa0cec397e639aed.png

⑤创建扫描注解的类,将自动注册的注解添加到注解处理器上以实现自动注册效果。

6e60dc10c37f425ab143cdd54b7f81bd.png

 ⑥重写init方法,输出Hello,APT

AbstractProcessor的init方法提供了一个环境对象processingEnv,从这个对象中可以得到一系列的工具以及获取到外部传入的参数。

注意: 这里是JavaLib,所以不能使用Log打印,这里可以使用Java的println()或注解处理器提供的方法,建议使用注解处理器提供的。

d92c7e4dba9044608a46442fe14e0469.png

 现在已经完成了APT的基本配置,可以build一下项目了,成败在此一举

c6d8ed2509764e2996964215d1eda595.png

 注意:

如果你已经成功输出了文本,说明APT已经配置好,可以继续下一步了

如果你失败了:

①如果继承的时候找不到AbstractProcessor类,那你 创建的肯定不是JavaLibrary,你可以删掉重新创建

②如果点击编译没反应,你可以试试先 clear一下项目再重新编译

③如果都不行,就去检查一下前面流程的 依赖是否都配置正确

2)继续完成功能

现在可以继续完成上面要实现的功能了。

①需要先来实现几个方法:

/**要扫描的注解,可以添加多个 */

@Override

public Set<String> getSupportedAnnotationTypes() {

    HashSet<String> hashSet = new HashSet<>();

    hashSet.add(Print.class.getCanonicalName());

    return hashSet;

}

/** 编译版本,固定写法就可以*/

@Override

public SourceVersion getSupportedSourceVersion() {

    return processingEnv.getSourceVersion();

}

9b01ebc331364f4a9d6bb2e958b1e34b.png

② 定义注解

先在MianActivity中添加两个成员变量并使用定义的注解

9824c6cb4dc24704a22007d064c67011.png

 ③解析注解

真正解析注解的地方是在process方法,先试试能不能拿到被注解的变量名:

/* 扫描注解回调 */

@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    //拿到所有添加Print注解的成员变量

    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Print.class);

    for (Element element : elements) {

        Name simpleName = element.getSimpleName(); //拿到成员变量名

        processingEnv.getMessager( ).printMessage(Diagnostic.Kind.NOTE,simpleName); //输出成员变量名

    }

    return false;

}

30810ea67b044af2b4f00104c2d1dd8c.png

 process方法有两个参数,第一个参数表示需要处理的注解的集合,即创建自定义注解处理器时MyProcessor中所定义的类。第二个参数是APT框架提供的查询程序元素的工具,如通过roundEnv.getElementsAnnotatedWith可以查询到程序中所有标注了某注解的类。

现在编译试一下:

2ceb9873c6484c079e6d3ef87e11a2dc.png

 ④生成类

既然能拿到被注解的变量名,后面就简单了,只需要用字符串拼出来一个工具类,然后用IO流写到本地就ok了

7931166c9c924596b5b98fdaffce7f75.png

 现在点击一下编译,可以看到app模块下的build文件已经有生成的类了

03689d2e30ae48cdbac8acf94ac3ffc6.png

 ⑤调用方法

现在回到MainActivity,就可以直接调用这个动态生成的类了

816903011718493ea6955e4980ed2fa7.png

3.javaPoet

JavaPoet可以帮我们以面向对象的思维来生成类,这样就不用手动拼接字符串的方式来生成类了,优化一下上面的代码:

先添加依赖

implementation 'com.squareup:javapoet:1.13.0'

e09be283d08d4140b2c0a1848a3153e9.png

 /**扫描注解回调*/

@Override

public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

    //拿到所有添加Print注解的成员变量

    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Print.class);

    //生成类

    TypeSpec.Builder classBuilder = TypeSpec

            .classBuilder("PrintUtil")

            .addModifiers(Modifier.PUBLIC, Modifier.FINAL);

    for (Element element : elements) {

        //拿到成员变量名

        Name simpleName = element.getSimpleName();

        //生成方法

        MethodSpec method = MethodSpec.methodBuilder("print$$"+simpleName)

                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)

                .returns(void.class)

                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")

                .build();

        classBuilder.addMethod(method);

    }

    //包

    JavaFile javaFile = JavaFile

            .builder("com.lkx.helloapt", classBuilder.build())

            .build();

    try {

        javaFile.writeTo(processingEnv.getFiler());

    } catch (IOException e) {

        e.printStackTrace();

    }

    return false;

}

dbe234b54c4c45f791118ad139b0ab65.png

 编译一下即可生成PrintUtil类

4.总结

①APT可以在编译器扫描注解提前生成类

②JavaPoet可以优雅的生成类,再也不用拼接了

③APT最主要的功能就是可以替代反射的一些功能,避免降低性能

④APT只会在编译时影响一点点速度,在运行期不会,而反射刚好相反

猜你喜欢

转载自blog.csdn.net/zenmela2011/article/details/126099209
apt