美文网首页Android APT
Android开发—APT注解处理器详解

Android开发—APT注解处理器详解

作者: wenzhihao123 | 来源:发表于2019-05-05 14:50 被阅读0次

    关于APT

    APT(Annotation Processing Tool)是一种注解处理工具,它会对源文件进行扫描找出相应的Annotation并在注解处理器中进行操作,具体操作由注解处理器也就是用户自己去实现,比如可以生成一些新的文件或者其他文件等,最终会把新生成的文件和源文件一起进行编译。

    APT工具常用的有2个,android-apt和Gradle2.2以后的annotationProcessor功能。

    APT处理annotation的基本流程表示:

    • 定义注解,比如@Route
    • 自定义注解处理器,处理注解(如生成java文件等)
    • 使用注解处理器
    APT操作@Route注解的大致步骤

    android-apt

    官方文档

    一个Gradle插件帮助Android Studio处理annotation processors,Gradle2.2以后Gradle提供annotationProcessor的功能可以完全代替android-apt,android-apt官网上作者也说明了,不再维护,并且谷歌明确表示Gradle 3.0.0+ 不再支持 android-apt 插件,所以推荐使用annotationProcessor。

    android-apt主要有2个目的:
    1、允许在注解处理器编译的时候当做依赖,但是在打包apk或者当做类库的时候不会打到里面;
    2、设置生成的资源路径以便能被Android studio正确访问到;

    使用插件的时候如下配置gradle脚本

    buildscript {
        repositories {
          mavenCentral()
        }
        dependencies {
            // replace with the current version of the Android plugin
            classpath 'com.android.tools.build:gradle:1.3.0'
            // the latest version of the android-apt plugin
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        }
    }
    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    

    传递编译的参数

    apt {
        arguments {
                resourcePackageName android.defaultConfig.applicationId
                androidManifestFile variant.outputs[0]?.processResources?.manifestFile
        }
    }
    

    由于android-apt已经过时了,并且annotationProcessor也正式被Google扶正,所以具体apt的使用不在进行演示,有兴趣的同学可以访问android-apt主页进行学习。

    annotationProcessor

    官方文档
    annotationProcessor是Gradle2.2+内置的功能,不需要额外引入其他插件,可以向下面这样直接在gradle文件引入。

    dependencies {
        // Adds libraries defining annotations to only the compile classpath.
        compileOnly 'com.google.dagger:dagger:version-number'
        // Adds the annotation processor dependency to the annotation processor classpath.
        annotationProcessor 'com.google.dagger:dagger-compiler:version-number'
    }
    

    这是引用第三方的注解处理器,我们实际开发中可以自定义注解处理器,下面我们自定义一个简单的注解处理器。

    自定义注解处理器

    自定义注解处理器的话需要用到2个第三方库AutoServiceJavaPoet ,还有Java自带的AbstractProcessor。

    • AbstractProcessor:Java内置注解处理器,注解处理器核心工作都在这个类进行。
    • AutoService:Google开源用来自动注册我们自己的注解处理器。
    • JavaPoet:Java代码生成器,方便我们生成Java文件;

    我们按照上文说的APT处理annotation的基本流程来自定义。

    1、定义注解,比如@Route
    新建项目,然后新建一个Java module,叫annotationLib,里面定义我们自己的注解,关于注解的相关知识这里不再细说具体可以参考这里

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.CLASS)
    @interface Route {
        String value() ;
    }
    

    2、自定义注解处理器,处理注解(如生成java文件等)
    再新建一个Java module,叫annotationCompiler,里面实现具体的注解处理器,配置gradle文件引入 AutoServiceJavaPoet,再依赖上我们之前定义的注解模块。

    apply plugin: 'java-library'
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        implementation 'com.google.auto.service:auto-service:1.0-rc2'
        implementation 'com.squareup:javapoet:1.8.0'
        implementation project(':annotationLib')
    }
    sourceCompatibility = "7"
    targetCompatibility = "7"
    
    

    新建一个注解处理器继承自AbstractProcessor:

    @AutoService(Processor.class)
    public class RouteProcessor extends AbstractProcessor {
        private Messager messager;
        private Filer filer;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            messager = processingEnvironment.getMessager();
            filer = processingEnvironment.getFiler();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> annotataions = new HashSet<String>();
            annotataions.add(Route.class.getCanonicalName());
            return annotataions;
        }
    
        public void loggerInfo(String msg) {
            messager.printMessage(Diagnostic.Kind.NOTE, msg);
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            if (set != null && !set.isEmpty()) {
                loggerInfo("process start");
                StringBuilder printInfo = new StringBuilder();
                Set<? extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith(Route.class);
                try {
                    if (routeElements != null && routeElements.size() > 0) {
                        printInfo.append(routeElements.size() + "个文件加了@Route注解!");
                    }
                } catch (Exception e) {
                    loggerInfo(e.getMessage());
                }
                //构建参数
                ParameterSpec msg = ParameterSpec.builder(String.class, "msg")
                        .build();
                //构建方法
                MethodSpec method = MethodSpec.methodBuilder("inject")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                        .returns(void.class)
                        .addParameter(msg)
                        .addStatement("$T.out.println($S+msg)", System.class, printInfo.toString())
                        .build();
                //构建类
                TypeSpec helloWorld = TypeSpec.classBuilder("InjectHelper")
                        .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                        .addMethod(method)
                        .build();
                 //构建文件并指定生成文件目录
                JavaFile javaFile = JavaFile.builder("com.wzh.annotation", helloWorld)
                        .build();
                loggerInfo("process end");
                try {
                    //把类、方法、参数等信息写入文件
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    loggerInfo("process exception");
                    e.printStackTrace();
                }
                return true;
            }
            return false;
        }
    }
    
    

    然后我们在我们app模块引用这个注解编译器及注解,如下:

    dependencies {
         ...
        annotationProcessor project(':annotationCompiler')
        implementation project(':annotationLib')
        ...
    }
    

    我们clean一下项目,然后rebuild一下,会发现如下目录生成的文件:

    app/build/generated/source/apt/debug/com/wzh/annotation/InjectHelper.java

    import java.lang.String;
    import java.lang.System;
    
    public final class InjectHelper {
      public static void inject(String msg) {
        System.out.println("2个文件加了@Route注解!"+msg);
      }
    }
    

    这就是我们生成的文件,很简单一个InjectHelper类,里面一个静态方法inject,打印出加@Route的文件个数。

    3、使用注解处理器

    @Route("main")
    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            InjectHelper.inject("调用生成类的方法");
        }
    }
    
    @Route("test")
    public class TestClass {
         //empty class
    }
    

    这里我们演示很简单,在Activity上加了@Route的注解,并调用生成类的方法,然后在任意类都可以加注解,因为我们没做任何注解类的限制,运行程序输出:

    System.out: 2个文件加了@Route注解!调用生成类的方法

    Kotlin使用注解处理器

    首先app模块引入注解处理器的时候需要引入kapt插件,在app下的gradle配置如下:

    apply plugin: 'kotlin-kapt'
    ....
    dependencies {
         ...
         //自定义注解处理器 module
        kapt project(':annotationCompiler')
        //自定义注解 module
        implementation project(':annotationLib')
        ...
    }
    

    其他配置基本一样,文件生成的目录变化,apt目录变为kapt

    app/build/generated/source/kapt/debug/com/wzh/annotation/InjectHelper.java

    给注解处理器传参数

    在编译之前可以传递需要的参数给注解处理器,我们在app模块gradle传递module的名字给注解处理器:

    android {
        defaultConfig {
            ...
            javaCompileOptions {
                annotationProcessorOptions {
                     //参数名 route_module_name,携带的数据就是当前module的名字
                    arguments = [route_module_name: project.getName()]
                }
            }
        }
    }
    

    在注解处理器init方法里接受参数:

    private String moduleName = null;
    
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        ...
        moduleName = processingEnvironment.getOptions().get("route_module_name");
        loggerInfo("moduleName = " + moduleName);
    }
    
    

    Rebuild项目的时候我们会在build控制台看到如下输出信息:

    moduleName = app

    APT的相关知识学习

    自定义AbstractProcessor的时候我们会重写以下的方法:

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        // 初始化操作
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 设置注解处理器需要处理的注解类型
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
         //指定java版本
         return SourceVersion.latestSupported();
    }
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //注解处理的核心方法
    }
    

    下面重点介绍initprocess方法

    • init(ProcessingEnvironment processingEnvironment) 方法
      此方法会被注解处理工具调用,参数ProcessingEnvironment 提供了一些实用的工具类Elements、Types和Filer等,如下表所示。
    工具方法 功能
    getElementUtils() 返回实现Elements接口的对象,用于操作元素的工具类
    getFiler() 返回实现Filer接口的对象,用于创建文件、类和辅助文件
    getMessager() 返回实现Messager接口的对象,用于报告错误信息、警告提醒
    getOptions() 返回指定的参数选项,可在Gradle文件配置
    getTypeUtils() 返回实现Types接口的对象,用于操作类型的工具类
    • process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)方法
      此方法里面是我们进行注解处理逻辑的地方。
      参数1 Set<? extends TypeElement> set:返回所有当前注解处理器需要处理的Annotation.
      参数2 RoundEnvironment roundEnvironment:表示当前或是之前的运行环境,可以通过该对象查找到注解。

    从roundEnvironment我们可以获取到Element被注解的元素信息。下面我们写个实例来打印一下看看。

    package com.wzh.annotation;
    @Retention(RetentionPolicy.CLASS)
    @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD,
    ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE, ElementType.ANNOTATION_TYPE,ElementType.TYPE_PARAMETER})
    public @interface Test {
        String value();
    }
    

    这里定义的注解为了方便打印,支持注解到类、方法、变量、参数等。下面使用注解。

    package com.wzh.annotation;
    @Test("this is class TestClass")
    public class TestClass<T> implements TestInterface{
        @Test("this is local field name")
        private String name = "my name is test";
    
        @Test("this is local method sayHello")
        private String sayHello(@Test("this is parameter msg") String msg){
            String hello = "my name is hello";
            return hello;
        }
    }
    
    

    然后在注解处理器去打印元素信息。

    private void parseTestAnnotation(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Test.class);
            for (Element element : elements){ //遍历所有元素
                if(element.getKind().equals(ElementKind.PACKAGE)){
                    LoggerInfo("element--------------PACKAGE-------------------------------");
                } else if (element.getKind().equals(ElementKind.CLASS)){
                    //被注解的元素是类
                    TypeElement typeElement = (TypeElement) element;
                    LoggerInfo("element--------------CLASS-------------------------------");
                    //实现接口信息
                    LoggerInfo("element:Interfaces = "+typeElement.getInterfaces().toString());
                    //泛型参数
                    LoggerInfo("element:TypeParameters = "+typeElement.getTypeParameters().toString());
    
                    //element的父元素是包元素
                    PackageElement packageElement = (PackageElement) element.getEnclosingElement();
                    LoggerInfo("element:packageElement = "+packageElement.getQualifiedName());
    
                } else if (element.getKind().equals(ElementKind.FIELD)){
                    //被注解的元素是全局变量
                    LoggerInfo("element--------------FIELD-------------------------------");
                    VariableElement variableElement = (VariableElement) element;
                    //获取变量类型
                    LoggerInfo("element:typeSimpleName = "+ types.asElement(variableElement.asType()).getSimpleName());
                } else if (element.getKind().equals(ElementKind.PARAMETER)){
                    //被注解的元素是参数
                    LoggerInfo("element--------------PARAMETER-------------------------------");
                } else if (element.getKind().equals(ElementKind.METHOD)){
                    //被注解的元素是方法
                    LoggerInfo("element--------------METHOD-------------------------------");
                    ExecutableElement executableElement = (ExecutableElement) element;
                    //获取方法的参数名
                    LoggerInfo("element:Parameters = "+executableElement.getTypeParameters().toString());
                    //获取方法的返回值类型
                    LoggerInfo("element:ReturnType = "+executableElement.getReturnType().toString());
                }
                //打印注解里面的值
                LoggerInfo("element:value = "+ element.getAnnotation(Test.class).value());
                //打印包名信息
                LoggerInfo("element:packageName = "+ elementUtils.getPackageOf(element).getQualifiedName());
                //被注解元素的名称
                LoggerInfo("element:SimpleName = "+element.getSimpleName());
                //被注解元素的类型(String/int/float...)
                LoggerInfo("element:asType = "+element.asType().toString());
                //被注解元素的种类(PACKAGE、CLASS、METHOD、PARAMETER等)
                LoggerInfo("element:KindName = "+element.getKind().name());
                //获取父元素的种类(局部变量的父元素是方法、方法及全局变量的父元素是类、类元素的父元素是包)
                LoggerInfo("element:EnclosingElementKindName = "+element.getEnclosingElement().getKind().name());
                //被注解元素的修饰 如:public static 等
                LoggerInfo("element:Modifiers = "+element.getModifiers().toString());
            }
        }
    

    注: >> element--------------CLASS-------------------------------
    注: >> element:Interfaces = com.wzh.annotation.TestInterface
    注: >> element:TypeParameters = T
    注: >> element:packageElement = com.wzh.annotation
    注: >> element:value = this is class TestClass
    注: >> element:packageName = com.wzh.annotation
    注: >> element:SimpleName = TestClass
    注: >> element:asType = com.wzh.annotation.TestClass<T>
    注: >> element:KindName = CLASS
    注: >> element:EnclosingElementKindName = PACKAGE
    注: >> element:Modifiers = [public]
    注: >> element--------------FIELD-------------------------------
    注: >> element:typeSimpleName = String
    注: >> element:value = this is local field name
    注: >> element:packageName = com.wzh.annotation
    注: >> element:SimpleName = name
    注: >> element:asType = java.lang.String
    注: >> element:KindName = FIELD
    注: >> element:EnclosingElementKindName = CLASS
    注: >> element:Modifiers = [private]
    注: >> element--------------METHOD-------------------------------
    注: >> element:Parameters =
    注: >> element:ReturnType = java.lang.String
    注: >> element:value = this is local method sayHello
    注: >> element:packageName = com.wzh.annotation
    注: >> element:SimpleName = sayHello
    注: >> element:asType = (java.lang.String)java.lang.String
    注: >> element:KindName = METHOD
    注: >> element:EnclosingElementKindName = CLASS
    注: >> element:Modifiers = [private]
    注: >> element--------------PARAMETER-------------------------------
    注: >> element:value = this is parameter msg
    注: >> element:packageName = com.wzh.annotation
    注: >> element:SimpleName = msg
    注: >> element:asType = java.lang.String
    注: >> element:KindName = PARAMETER
    注: >> element:EnclosingElementKindName = METHOD
    注: >> element:Modifiers = []

    由打印结果可以看到所有被注解的元素信息都被打印出来。

    Element

    Java文档关于Element介绍
    Element代表一个程序元素,如包、类、方法、变量、参数、接口泛型等的接口,其有多种子类分别代表不同的程序元素,如:ExecutableElement 方法元素, PackageElement 包元素, TypeElement 类元素, TypeParameterElement 形参元素, VariableElement 变量及参数元素等。之前的TestClass<T>可以对应成下图。

    Element对应图
    TypeMirror

    TypeMirror是一个接口,表示Java编程语言中的类型。这些类型包括基本类型、引用类型、数组类型、类型变量和null类型等等。Element的 asType()返回TypeMirror类型的值,我们通过这个值得getKind()方法获取元素的类型,这个类型有很多枚举类型如:CHARARRAY(数组)FLOATEXECUTABLE(方法)等。

    总结

    关于注解处理器具体使用还有很多东西,就不一一写出来了,具体可以参考JAVA API,不得不说注解处理器很强大,很多热门框架都使用了APT,如:butterknife、Arouter、Dagger2、EventBus等。所以学好注解处理器还是比较重要的,接下来我们实战一把,不看butterknife源码的情况下实现简单的功能。Android开发— APT之ButterKnife的简单功能实现

    参考:
    Java API
    Java注解处理器
    java-apt的实现之Element详解

    相关文章

      网友评论

        本文标题:Android开发—APT注解处理器详解

        本文链接:https://www.haomeiwen.com/subject/erthvqtx.html