Android APT Series (3): Exploration of APT Technology

Introduction to APTs

What are APTs?

The full name of APT is Annotation Processing Tool, which translates to annotation processor. Quoting an official introduction to APT: APT is a tool for processing annotations. It detects source code files to find annotations in them, and uses annotations for additional processing.
What are APTs for?

APT can automatically generate code for us according to the annotations in the compilation phase during compilation, simplifying the use. APT technology is used in many popular frameworks, such as ButterKnife, Retrofit, Arouter, EventBus, etc.
APT project
1), APT project creation

In general, the general implementation process of APT:
1. Create a Java Module to write annotations
2. Create a Java Module to read annotation information and generate corresponding class files according to specified rules
3. Create An Android Module, which obtains the generated class through reflection, performs reasonable encapsulation, and provides it to the upper layer to call. As shown in the
figure below:
insert image description hereThis is my APT project. The name of the Module can be chosen arbitrarily, just follow the rules I mentioned above.

2), Module dependency

After the project is created, we need to clarify a dependency between each Module:

1. Because apt-processor needs to read the annotations of apt-annotation, apt-processor needs to rely on apt-annotation

//apt-processor 的 build.gradle 文件
dependencies {
    
    
    implementation project(path: ':apt-annotation')
}

2. The app is used as the calling layer, and the above three Modules all need to be depended on

//app 的 build.gradle 文件
dependencies {
    
    
    //...
    implementation project(path: ':apt-api')
    implementation project(path: ':apt-annotation')
    annotationProcessor project(path: ':apt-processor')
}

After the APT project is configured, we can write a specific code for each Module and write
apt-annotation annotations

The processing of this Module is relatively simple, just write the corresponding custom annotations, I wrote as follows:

@Inherited
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({
    
    ElementType.TYPE,ElementType.METHOD})
public @interface AptAnnotation {
    
    
    String desc() default "";
}

apt-processor automatically generates code

This Module is relatively complicated, and we divide it into the following three steps:
1. Annotation processor declaration
2. Annotation processor registration
3. Annotation processor generation class file
1) Annotation processor declaration

1. Create a new class, choose the class name according to your preferences, inherit the AbstractProcessor class under the javax.annotation.processing package and implement its abstract method

public class AptAnnotationProcessor extends AbstractProcessor {
    
    
  
    /**
     * 编写生成 Java 类的相关逻辑
     *
     * @param set              支持处理的注解集合
     * @param roundEnvironment 通过该对象查找指定注解下的节点信息
     * @return true: 表示注解已处理,后续注解处理器无需再处理它们;false: 表示注解未处理,可能要求后续注解处理器处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        return false;
    }
}

Focus on the TypeElement in the first parameter, which involves the knowledge of Element. Let’s briefly introduce it:
Introduction to Element
In fact, Java source files are a structural language, and each part of the source code corresponds to a Specific types of Elements, such as packages, classes, fields, methods, etc.:

package com.dream;         // PackageElement:包元素

public class Main<T> {
    
         // TypeElement:类元素; 其中 <T> 属于 TypeParameterElement 泛型元素

    private int x;         // VariableElement:变量、枚举、方法参数元素

    public Main() {
    
            // ExecuteableElement:构造函数、方法元素
    }
}

Java's Element is an interface, the source code is as follows:

public interface Element extends javax.lang.model.AnnotatedConstruct {
    
    
    // 获取元素的类型,实际的对象类型
    TypeMirror asType();
    // 获取Element的类型,判断是哪种Element
    ElementKind getKind();
    // 获取修饰符,如public static final等关键字
    Set<Modifier> getModifiers();
    // 获取类名
    Name getSimpleName();
    // 返回包含该节点的父节点,与getEnclosedElements()方法相反
    Element getEnclosingElement();
    // 返回该节点下直接包含的子节点,例如包节点下包含的类节点
    List<? extends Element> getEnclosedElements();

    @Override
    boolean equals(Object obj);
  
    @Override
    int hashCode();
  
    @Override
    List<? extends AnnotationMirror> getAnnotationMirrors();
  
    //获取注解
    @Override
    <A extends Annotation> A getAnnotation(Class<A> annotationType);
  
    <R, P> R accept(ElementVisitor<R, P> v, P p);
}

We can obtain some of the above information through Element (the ones with comments are commonly used).
There are 5 types of extension classes derived from Element:
1. PackageElement represents a package program element
2. TypeElement represents a class or interface program element
3. TypeParameterElement represents a generic element
4, VariableElement represents a field, enum constant, method or constructor parameter, local variable or exception parameter
5, ExecutableElement represents a class or interface method, constructor or initializer (static or instance)
It can be found that Element sometimes represents multiple elements. For example, TypeElement represents a class or interface. At this time, we can distinguish it by element.getKind():

Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);
for (Element element : elements) {
    
    
    if (element.getKind() == ElementKind.CLASS) {
    
    
        // 如果元素是类

    } else if (element.getKind() == ElementKind.INTERFACE) {
    
    
        // 如果元素是接口

    }
}

ElementKind is an enumeration class with many values, as follows:

PACKAGE //表示包
ENUM //表示枚举
CLASS //表示类
ANNOTATION_TYPE //表示注解
INTERFACE //表示接口
ENUM_CONSTANT //表示枚举常量
FIELD //表示字段
PARAMETER //表示参数
LOCAL_VARIABLE //表示本地变量
EXCEPTION_PARAMETER //表示异常参数
METHOD //表示方法
CONSTRUCTOR //表示构造函数
OTHER //表示其他

This is the introduction to Element, let's look down

2. Interpretation of rewriting methods
In addition to the abstract method that must be implemented, we can also rewrite other 4 commonly used methods, as follows:

public class AptAnnotationProcessor extends AbstractProcessor {
    
    
    //...
  
    /** 
     * 节点工具类(类、函数、属性都是节点)
     */
    private Elements mElementUtils;

    /** 
     * 类信息工具类
     */
    private Types mTypeUtils;

    /**
     * 文件生成器
     */
    private Filer mFiler;

    /**
     * 日志信息打印器
     */
    private Messager mMessager;
  
    /**
     * 做一些初始化的工作
     * 
     * @param processingEnvironment 这个参数提供了若干工具类,供编写生成 Java 类时所使用
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    
    
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
        mTypeUtils = processingEnv.getTypeUtils();
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
    }
  
    /**
     * 接收外来传入的参数,最常用的形式就是在 build.gradle 脚本文件里的 javaCompileOptions 的配置
     *
     * @return 属性的 Key 集合
     */
    @Override
    public Set<String> getSupportedOptions() {
    
    
        return super.getSupportedOptions();
    }

    /**
     * 当前注解处理器支持的注解集合,如果支持,就会调用 process 方法
     *
     * @return 支持的注解集合
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        return super.getSupportedAnnotationTypes();
    }
        
    /**
     * 编译当前注解处理器的 JDK 版本
     *
     * @return JDK 版本
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        return super.getSupportedSourceVersion();
    }
}

Note: We can also use annotations to provide the three methods of getSupportedAnnotationTypes(), getSupportedSourceVersion() and getSupportedOptions():

@SupportedOptions("MODULE_NAME")
@SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AptAnnotationProcessor extends AbstractProcessor {
    
    
    //...
}

2), annotation processor registration

The annotation processor is declared, and the next step is to register it. There are two ways to register:
1. Manual registration
2. Automatic registration
Manual registration is cumbersome and error-prone. It is not recommended and will not be discussed here. We mainly look at automatic registration
automatic registration

1. First, we need to import the following dependencies into the build.gradle file under the Module apt-processor:

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

Note: These two sentences must be added, otherwise the registration will not be successful, I stepped on the pit before

2. Add @AutoService(Processor.class) to the annotation processor to complete the registration

@AutoService(Processor.class)
public class AptAnnotationProcessor extends AbstractProcessor {
    
    
    //...
}

3) Annotation processor generates class files

After the registration is completed, we can formally write the code to generate Java class files, and there are two ways to generate them:
1. The conventional way of writing files
2. The way of writing 1 through the javapoet framework
is relatively rigid, and each letter needs to be Write it down, it is not recommended to use it, so I won’t talk about it here. We mainly look at
the javapoet method of generating Java class files through the javapoet framework

This method is more in line with a style of object-oriented coding. Friends who are not familiar with javapoet can go to github to learn a wave of portals. Here we introduce some commonly used classes: TypeSpec: used to generate classes, interfaces,
enumerations The class MethodSpec of the object
: the class used to generate the method object
ParameterSpec: the class used to generate the parameter object
AnnotationSpec: the class used to generate the annotation object
FieldSpec: the class used to configure the generated member variable
ClassName: generated by the package name and class name Object, which is equivalent to specifying a Class in JavaPoet.
ParameterizedTypeName: generate a class containing generics through MainClass and IncludeClass
JavaFile: control the output class of the generated Java file
1. Import javapoet framework dependencies

implementation 'com.squareup:javapoet:1.13.0'

2. Generate Java class files according to the specified code template

For example, I have made the following configurations under app's build.gradle:

android {
    
    
    //...
    defaultConfig {
    
    
        //...
        javaCompileOptions {
    
    
            annotationProcessorOptions {
    
    
                arguments = [MODULE_NAME: project.getName()]
            }
        }
    }
}

The following annotations are made under MainActivity:

@AptAnnotation(desc = "我是 MainActivity 上面的注解")
public class MainActivity extends AppCompatActivity {
    
    

    @AptAnnotation(desc = "我是 onCreate 上面的注解")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAptApi.init();
    }
}@AptAnnotation(desc = "我是 MainActivity 上面的注解")
public class MainActivity extends AppCompatActivity {
    
    

    @AptAnnotation(desc = "我是 onCreate 上面的注解")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAptApi.init();
    }
}

The code I would like to generate is as follows:

public class HelloWorld {
    
    
  public void test(String param) {
    
    
    System.out.println("模块: apt-app");
    System.out.println("节点: MainActivity  描述: 我是 MainActivity 上面的注解");
    System.out.println("节点: onCreate  描述: 我是 onCreate 上面的注解");
  }
}

Now let's do it in action:

@AutoService(Processor.class)
@SupportedOptions("MODULE_NAME")
@SupportedAnnotationTypes("com.dream.apt_annotation.AptAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AptAnnotationProcessor extends AbstractProcessor {
    
    
        
    //文件生成器
    Filer filer;
    //模块名
    private String mModuleName;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
    
    
        super.init(processingEnvironment);
        //初始化文件生成器
        filer = processingEnvironment.getFiler();
        //通过 key 获取 build.gradle 中对应的 value
        mModuleName = processingEnv.getOptions().get("MODULE_NAME");
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
    
        if (set == null || set.isEmpty()) {
    
    
            return false;
        }
                
        //获取当前注解下的节点信息
        Set<? extends Element> rootElements = roundEnvironment.getElementsAnnotatedWith(AptAnnotation.class);

        // 构建 test 函数
        MethodSpec.Builder builder = MethodSpec.methodBuilder("test")
                .addModifiers(Modifier.PUBLIC) // 指定方法修饰符
                .returns(void.class) // 指定返回类型
                .addParameter(String.class, "param"); // 添加参数
        builder.addStatement("$T.out.println($S)", System.class, "模块: " + mModuleName);

        if (rootElements != null && !rootElements.isEmpty()) {
    
    
            for (Element element : rootElements) {
    
    
                //当前节点名称
                String elementName = element.getSimpleName().toString();
                //当前节点下注解的属性
                String desc = element.getAnnotation(AptAnnotation.class).desc();
                // 构建方法体
                builder.addStatement("$T.out.println($S)", System.class, 
                                     "节点: " + elementName + "  " + "描述: " + desc);
            }
        }
        MethodSpec main =builder.build();

        // 构建 HelloWorld 类
        TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                .addModifiers(Modifier.PUBLIC) // 指定类修饰符
                .addMethod(main) // 添加方法
                .build();

        // 指定包路径,构建文件体
        JavaFile javaFile = JavaFile.builder("com.dream.aptdemo", helloWorld).build();
        try {
    
    
            // 创建文件
            javaFile.writeTo(filer);
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

        return true;
    }
}

After the above steps, we can generate the code of the above screenshot by running the App, and now we are still in the final step, using the generated code

Note: The location of class files generated by different versions of Gradle may be different. My Gradle version is 6.7.1, and the generated class files are in the following locations: class files generated by some lower versions of Gradle are in the directory /
insert image description herebuild/generated/source Down

apt-api calls to generate code to complete business functions

The operation of this Module is relatively simple, that is, to obtain the generated class through reflection, and then encapsulate and use it accordingly. My writing is as follows:

public class MyAptApi {
    
    

    @SuppressWarnings("all")
    public static void init() {
    
    
        try {
    
    
            Class c = Class.forName("com.dream.aptdemo.HelloWorld");
            Constructor declaredConstructor = c.getDeclaredConstructor();
            Object o = declaredConstructor.newInstance();
            Method test = c.getDeclaredMethod("test", String.class);
            test.invoke(o, "");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

Then we call it in the oncreate method of MainActivity:

@AptAnnotation(desc = "我是 MainActivity 上面的注解")
public class MainActivity extends AppCompatActivity {
    
    
  
    @AptAnnotation(desc = "我是 onCreate 上面的注解")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyAptApi.init();
    }
}

//Print result
Module: app
Node: MainActivity Description: I am the annotation above MainActivity
Node: onCreate Description: I am the annotation above onCreate
Summary

Some key points in this article:
1. Different types of Modules that need to be created in the APT project and the dependencies between Modules
2. Java source files are actually a structural language, and each part of the source code corresponds to Create a specific type of Element
3. Use auto-service to automatically register the annotation processor
4. Use the javapoet framework to write the generated Java class files
5. Provide the functions of the generated classes to the upper layer through reflection and appropriate encapsulation transfer

Guess you like

Origin blog.csdn.net/qq_24252589/article/details/131403778