• 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

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

    6e60dc10c37f425ab143cdd54b7f81bd.png

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

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

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

    d92c7e4dba9044608a46442fe14e0469.png

     现在已经完成了APT的基本配置,可以build一下项目了,会成功输出Hello,APT

    c6d8ed2509764e2996964215d1eda595.png

     注意:

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

    如果失败了:

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

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

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

     

    2)继续完成功能

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

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

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

    @Override

    public Set getSupportedAnnotationTypes() {

        HashSet 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 annotations, RoundEnvironment roundEnv) {

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

        Set 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 annotations, RoundEnvironment roundEnv) {

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

        Set 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只会在编译时影响一点点速度,在运行期不会,而反射刚好相反。

  • 相关阅读:
    在Linux中进行K8s部署
    java基础入门(一)
    C语言框架FreeSwitch自定义事件介绍与使用示例
    Frames X for figma 组件库&设计系统 Local Variables下载
    Python编程实例-Pandas快速入门
    (十四)数据结构-图
    一文秒懂AGC/AVC,以及什么是光伏电站AGC,AVC装置?AGC,AVC装置的功能与用途?
    网络安全(黑客)自学
    服务器----阿里云服务器重启或关机,远程连接进不去,个人博客无法打开
    LearnOpenGL(四)之纹理
  • 原文地址:https://blog.csdn.net/zenmela2011/article/details/126099209