Words: 3790 words

Estimated reading time: 25 minutes

Read the guide

APT(Annotation Processing Tool) is a Tool for Processing annotations.

APT scans and processes annotations in the source code during compilation. According to annotations, APT can be used to automatically generate Java code during development, which reduces redundant code and manual code input process, improves coding efficiency, and makes the source code look clearer, concise and readable.

At present, many third-party open source frameworks use APT technology to reduce the duplication of developers. Common examples are ButterKnife, EventBus, etc.

This paper focuses on the explanation of practical applications. Taking a common page jump scenario in the development of Android APP as an example, it explains how to complete the development of AN APT project step by step from the process of building a project, APT data and function introduction, data extraction and automatic code generation.

Directory:

1

The concept of APT

A brief introduction to the concept of APT and its current application;

2

Application Scenario Example

Introduce a practical scenario and explain it step by step;

3

Build APT Project

How to build an APT project in Android Studio

4

Data types and concepts in APT

Brief introduction of APT common data types;

5

APT process disassembly

Break down APT process, how to get data needed for annotations;

6

JavaPoet automates code generation

How to generate.java code files based on the obtained annotation data;

7

Summary of development process

Schematic diagram of the whole process.

❶ –

The concept of APT

APT (Annotation Processor Tool) is a built-in Tool of JavAC for scanning and processing annotations at compile time. To put it simply, during source code compilation, the annotation processor is used to retrieve the annotation content in the source file.

Since the annotation handler works at compile time, we can use the annotation handler to do what we need at compile time. A common usage is to get the relevant annotation data at compile time and then generate the code source file on the fly.

Usually the annotation processor is used to automatically generate some regular repeated code, which solves the problem of manual writing repeated code and greatly improves the coding efficiency.

Many of the better known open source frameworks use this technique, such as ButterKnife, which solves the problem of manually writing a large number of findViewById methods for developers. Others, such as THE JDT used in GreenDao, are exactly the same as APT, but the IDE and tools are different.

❷ –

Application Scenario Example

1. Requirement scenario

In Android development, a jump to an Activity is a necessary action. When data is passed in an Intent, the code looks like this:

1Intent intent = new Intent(context, TestActivity.class); 2intent.putExtra("id", id); 3intent.putExtra("name", name); 4intent.putExtra("is", is); 5if (! (context instanceof Activity)) {6 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 7}8startActivity(intent);Copy the code

And get data in TestActivity:

1id = intent.getIntExtra("id", 0); 2name = intent.getStringExtra("name"); 3is = intent.getBooleanExtra("is", false);Copy the code

The problem with the above code is that you need to write similar code for each procedure, which is repetitive and time-consuming.

Keys need to be consistent when data is passed and parsed. We could use constants instead, but we’re going to define a lot of constants.

We want the above code to be generated automatically, and developers need only call a few more readable methods to achieve the above process.

2. The analysis

For this requirement scenario, we need to implement the following automation functions:

(1) Automatically generate a class for TestActivity called TestActivityFastBundle;

(2) provide constructor mode chain call, can be needed to assign values to variables;

(3) Provide a build method that returns an Intent object;

(4) Can jump to the Activity, support startActivity or startActivityForResult;

(5) Support calling an interface to parse the data passed in the Intent and assign values to the Activity.

We expect the simplified call to look like this, which will jump to TestActivity:

1new TestActivityFastBundle()2 .id(1)3 .is(true)4 .name("user")5 .launch(this); // Or use launchForResultCopy the code

In TestActivity, we expect to call:

1new TestActivityFastBundle().bind(this, getIntent());Copy the code

The implementation automatically assigns variables in the Intent to variables in the current class.

❸ –

Build APT Project

1. Create an Android Library and create your own annotation classes.

For example:

1@Retention(CLASS)2@Target(FIELD)3public @interface AutoBundle {4 boolean require() default false; 5}Copy the code

2. Create a Java Library, reference the Android Library created in Step 1, and add dependencies to the Java Library.

1 implementation 'com. Google. Auto. Services: auto - service: 1.0 rc2'Copy the code

Here’s what this library does:

Because annotation handlers work at compile time, they need to “register” with the compiler to let it know which annotator needs to be used to process data.

If the auto-service library is not used, the manual registration method is as follows:

1. Create the Resources folder in Library.

2. Create meta-INF and services folders in resources.

3. In the services to create a file, named javax.mail. The annotation. Processing. The Processor;

4. The javax.mail. The annotation. Processing. Processor file input created his annotation Processor class name (complete, including the package name).

3. Create your own processing class, inherit AbstractProcessor, and register it with Auto-service.

For example:

1@AutoService(Processor.class)2public class AutoBundleProcessor extends AbstractProcessorCopy the code

Now that we subclass AbstractProcessor, we need to override a few of these methods to implement our own processing logic:

1@Override2public synchronized void init(ProcessingEnvironment processingEnvironment)3Copy the code

The ProcessingEnvironment class contains the data objects that we need to parse, and we can use it to retrieve a list of other objects that we need to retrieve the data that we need.

1@Override2public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)Copy the code

The process method calls back during compilation to retrieve the classes, objects, and corresponding annotations we need, analyze and process the data, and ultimately generate the code we need.

1@Override2public Set<String> getSupportedAnnotationTypes()Copy the code

GetSupportedAnnotationTypes method is needed to help us get comments. We put the class name we want into the Set and return it to the annotation handler, in other words, specify which annotations the annotation handler needs to work with.

4. Refer to it in your project

The Android Library that contains annotations is referenced in the main gradle project to reference the Java Library where the annotator is located. Because of kotlin’s introduction, it is recommended to use kapt instead of annotationProcessor.

For example:

1kapt project(':libProce')Copy the code

At this point, the overall structure of the project has been built.

The following sections will introduce the functions of various classes and objects in APT and how to achieve the functions we need.

– ❹ –

Data types and concepts in APT

1.ProcessingEnvironment

When we override an AbstractProcessor init method in a subclass, the argument is a ProcessingEnvironment object. It internally provides useful objects such as Elements, Types, and Filer, all of which play an important role in APT. We can get these objects to do what we want.

2.Element

In APT, everything is called an element. An object, a class, a method, a parameter. In APT, they are all collectively called elements. Element itself is an interface, but there are subclasses, such as TypeElement and VariableElement, on which additional interface methods are added to describe special properties of specific things.

3.ElementKind

In APT, everything is called an element, so we need to know what an element is, and we can judge it by ElementKind.

ElementKind is an enumerated class. Including but not limited to PACKAGE, CLASS, INTERFACE, FIELD, PARAMETER, METHOD, etc. These are the basic concepts in our development.

4.Elements

Elements can be understood as a utility class that operates on an Element object and performs some processing or evaluation on it.

5.TypeElement

TypeElement is an Element subclass that indicates that the Element is a class or interface. When an Element meets the criteria, you can force it into a TypeElement object.

6.VariableElement

A VariableElement is a subclass of Element that indicates that the Element is a variable, constant, method, constructor, parameter, etc. When an Element satisfies a condition, it can be forcibly converted to a VariableElement object.

7.Filer

Filer is an interface for file operations that can create or write a Java file. Mainly for Java file objects, and the general file difference is that this is specialized in dealing with Java class files, with. Java or.class suffix files. In APT, if we need to generate a.java or.class file after automatic code generation, Filer is needed.

8.Name

The Name class is a subclass of CharSequence and represents the Name of the class and method. In most cases, you can think of it as equivalent to String.

9.Types

Types can be understood as a utility class, which is a type manipulation tool. In the APT phase, we need to know whether a variable is int or Boolean, and that will need to be handled by the Types related class. It can manipulate TypeMirror objects.

10.TypeMirror

TypeMirror indicates the data type. Basic types such as int and Boolean can also represent complex data types such as custom classes, arrays, and Parcelable.

11.Modifier

Modifiers. For example, when declaring a variable, private static final are all modifiers. Most of the ones that Android Studio colors blue are modifiers (except for class Int Interface).

Note: If a variable in a class has a default scope, the modifier is default.

12.RoundEnvironment

When we override AbstractProcessor’s process method in a subclass, the argument is a RoundEnvironment object. The Element that we annotated in our code is available through the RoundEnvironment object.

❺ –

APT process disassembly

Below, the scenarios mentioned above will be gradually disassembled for APT processing, and the required attributes will be finally obtained to prepare for the generation of automatic codes.

Set annotations on variables in TestActivity:

1@AutoBundle2public int id; 3@AutoBundle4public String name; 5@AutoBundle6public boolean is;Copy the code

AutoBundle annotations are our own annotation classes.

After the initial design, we need to rewrite our logic in the process method:

1@Override2public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)Copy the code

The first step:

Gets all elements declared by the AutoBundle annotation. Here we know that there are really only three variables;

1for (Element element : roundEnvironment.getElementsAnnotatedWith(AutoBundle.class)) {23}Copy the code

The second step:

For each Element object in the loop, get its data information;

1if (element.getkind () == elementkind.field) {2 VariableElement = (VariableElement) Element; 4 / / for variables in the class information TypeElement object 5 TypeElement TypeElement = (TypeElement) variableElement. GetEnclosingElement ();Copy the code

The data contained in a variableElement includes modifiers, types, variable names, and so on;

TypeElement contains data such as class name, package name, and so on.

1 / / get the name of the class 2 string className = typeElement. GetSimpleName (), toString (); 5String packageName = elements.getpackageof (typeElement).getqualifiedname ().tostring (); 6 7 / / for annotations on the variable information 8 autoBundle autoBundle = variableElement. GetAnnotation (autoBundle. Class); 9boolean require = autoBundle.require(); 1011 / / get the variable name 12 name name = variableElement. GetSimpleName (); 15TypeMirror type = variableElement.astype ();Copy the code

For a variable we defined above, for example:

1@AutoBundle(require = true)2public int id;Copy the code

Then after obtaining the data:

1require = true2name = "id" 3type = int.classCopy the code

The same goes for the other two variables.

Three cycles will get us all the information we need.

Includes the annotation value, variable name, and type of the three variables. We also get the class name and package name for TestActivity. Some encapsulation and caching of this data can be done. Now you can automate code generation.

I encapsulate the above variable values into ClassHoder and FieldHolder classes. ClassHolder holds the class name, package name, etc., and FieldHolder holds each variable type, variable name, annotation, etc. Now you’ll use this saved data to generate code from JavaPoet.

– ❻ –

JavaPoet code generation is automated

JavaPoet is Java code automatically generated framework, it is a lot of open source projects, address: https://github.com/square/javapoet.

JavaPoet simplifies the development of Java code generation and makes calls more user-friendly and readable through the builder mode. With automatic import function, do not need to specify manually.

In JavaPoet, most data types use common types in APT, which is very convenient and fast to generate code with APT automation.

1.TypeSpec.Builder

TypeSpec.Builder is the building class of classes, where the class is generalized and can be a class, interface, annotation, etc.

methods

function

classBuilder

class

annotationBuilder

annotations

interfaceBuilder

interface

anonymousClassBuilder

An anonymous class

Sample code:

1TypeSpec.Builder contentBuilder = TypeSpec.classBuilder("yourClassName")Copy the code

2.MethodSpec.Builder

Methodspec.builder is the building class for methods.

methods

function

constructorBuilder

A constructor

methodBuilder

methods

Sample code:

1MethodSpec.Builder bindMethodBuilder = MethodSpec.methodBuilder("yourMethodName")Copy the code

3.FieldSpec.Builder

Fieldspec. Builder is the build class for variables.

methods

function

builder

Create a variable

Sample code:

1FieldSpec.Builder fieldBuilder = FieldSpec.builder(ClassName.get(field.getType()), "yourFieldName", Modifier.PRIVATE)Copy the code

4.JavaFile.Builder

methods

function

builder

Create a JavaFile object

writeTo

Write data as Java files, including Filer objects and paths

Sample code:

1JavaFile javaFile = JavaFile.builder(classHolder.getPackageName(), contentBuilder.build())2javaFile.writeTo(mFiler);Copy the code

5. All kinds of Builder methods

methods

function

describe

addModifier

Add modifiers

Modifier Corresponds to the Modifier class in Java, and all variables in the class are available. Such as private, public, protected, static, final, etc.

Example:

addModifiers(Modifier.PUBLIC)

addParameter

Add parameters

Parameters in a method.

Example:

addParameter(ClassName.get(“android.content”,  “Intent”), “intent”)

addParameter(int.class, “requestCode”)

addStatement

Add a description

Add code statements directly.

Example:

addStatement(“return this”)

addCode

Add code text

Add code statements directly. The difference from addStatement is that addStatement is handled only in plain text, while addCode is handled in code language.

AddCode will automatically import the classes used and add a semicolon to the end of the statement.

Example:

addCode(“if (! (context instanceof $T)) {\n”, ClassName.get(“android.app”, “Activity”));

returns

Add return value

Add the return value for the method.

Example:

returns(void.class);   

returns(ClassName.get(“android.content”, “Intent”));

addMethod

Add methods

Adds a constructed method object to the class.

Example:

addMethod(methodBuilder.build());

addField

Add a variable

Add a variable to a class.

Example:

addField(fieldBuilder.build());

6. Code generation examples

Constructing code and generating results Example 1:

1for (FieldHolder field : fields) {2 FieldSpec f = FieldSpec.builder(ClassName.get(field.getType()), field.getName(), Modifier.PRIVATE)3 .build(); 4contentBuilder.addField(f);Copy the code
1private int id; 2private String name; 3private boolean is;Copy the code

Constructing code and generating results Example 2:

1MethodSpec.Builder buildMethodBuilder = MethodSpec.methodBuilder("build") 2 .addModifiers(Modifier.PUBLIC) 3 .returns(ClassName.get("android.content", "Intent")); 4 5buildMethodBuilder.addParameter(ClassName.get("android.content", "Context"), "context"); 6 7buildMethodBuilder.addStatement(String.format("Intent intent = new Intent(context, %s.class)", classHolder.getClassName())); 8 9for (FieldHolder field : fields) {10 buildMethodBuilder.addStatement(String.format("intent.putExtra(\"%s\", %s)", field.getName(), field.getName())); 11}1213buildMethodBuilder.addCode("if (! (context instanceof $T)) {\n", ClassName.get("android.app", "Activity")); 1415buildMethodBuilder.addStatement("intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)"); 1617buildMethodBuilder.addCode("}\n"); 1819buildMethodBuilder.addStatement("return intent"); 2021contentBuilder.addMethod(buildMethodBuilder.build());Copy the code
1public Intent build(Context context) { 2 Intent intent = new Intent(context, TestActivity.class); 3 intent.putExtra("id", id); 4 intent.putExtra("name", name); 5 intent.putExtra("is", is); 6 if (! (context instanceof Activity)) { 7 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 8 } 9 return intent; 10}Copy the code

Constructing code and generating results Example 3:

1String fieldTypeName = field.getType().toString(); 2 3if (int.class.getName().equals(fieldTypeName) 4 || Integer.class.getName().equals(fieldTypeName)) { 5 6 builder.addStatement(String.format("target.%s = intent.getIntExtra(\"%s\", 0)", field.getName(), field.getName())); 7 8} else if (String.class.getName().equals(fieldTypeName)) { 910 builder.addStatement(String.format("target.%s = intent.getStringExtra(\"%s\")", field.getName(), field.getName())); 1112} else if (boolean.class.getName().equals(fieldTypeName)13 || Boolean.class.getName().equals(fieldTypeName)) {1415 builder.addStatement(String.format("target.%s = intent.getBooleanExtra(\"%s\", false)", field.getName(), field.getName())); 1617}Copy the code
1public void bind(TestActivity target, Intent intent) {2 target.id = intent.getIntExtra("id", 0); 3 target.name = intent.getStringExtra("name"); 4 target.is = intent.getBooleanExtra("is", false); 5}Copy the code

7. Write the generated code into a file

1JavaFile javaFile = JavaFile.builder(classHolder.getPackageName(), contentBuilder.build()).build(); 23try {4 javaFile.writeTo(mFiler); 5} catch (IOException e) {6 e.printStackTrace(); 7}Copy the code

Construct a JavaFile object, add the TypeSpecBuilder content, and write it to the Filer. Compiled this file was generated under the corresponding package, as shown in figure, automatically generated files in the build/generated under/source/kapt (compiled using kapt commands).

Generated code:


❼ –

Summary of development process


Fox friends technical team other wonderful articles

Overview of distributed tracking systems and comparison of mainstream open source systems

Don’t understand how GIF loading works? Just look at me!

Android permissions, do you really know that?

Swift Codable combat skills

AspectJ in Android


Join sohu technology author day group

Thousand yuan payment waiting for you!

Stamp here! ☛