SoFunction
Updated on 2025-04-10

Example of usage of annotation processor APT in Android

APT, Annotation Processing Tool, is an annotation processor, is a tool used to process annotations and is commonly used inCompile timeScan and process annotations, and finally generate a Java file that processes the annotation logic. APT technology is used in many current frameworks, such as ButterKnife, ARouter, GreenDAO and other frameworks, which have the shadow of APT technology.

APT function

Use APT to process compile-time annotations at compile time and generate additional Java files, with the following effects:

  • It can achieve the effect of reducing duplicate code handwriting.

    For example, ButterKnife, we can directly use annotations to reduce findviewbyid codes, and it is enough to just indicate which id it is through annotations.

  • Functional package. Encapsulate the main functional logic, leaving only annotation calls.

  • Compared with using Java reflection to handle runtime annotations, using APT has better performance.

Basic compilation process of Android

When compiling the code in Android, you need to go through the process of: Java—>class—> dex, and the code finally generates a dex file and is inserted into the APK package.

APT is involved at the beginning of compilation and is used to handle compile-time annotations.

AOP (Aspect Oridnted Programming) is to modify the code or add logic by directly modifying the .class file before generating the dex file after compilation. It is commonly used in code monitoring, code modification, and code analysis.

Basic use of APT

The basic usage process mainly includes the following steps:

  • Create custom annotations
  • Create annotation processors and process Java file generation logic
  • Encapsulate an API for external calls
  • Called in the project

This time, taking the imitation of ButterKnife binding View as an example, the code of findviewbyId is omitted, and the calling code is as follows:

public class AnnotationTestActivity extends AppCompatActivity {

    @BindView(.tv_annotation_test1)
    TextView tvAnnotationTest1;

    @BindView(.tv_annotation_test2)
    TextView tvAnnotationTest2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        (savedInstanceState);
        setContentView(.activity_annotation_test);
        (this);
        ("Test text");
        (new () {
            @Override
            public void onClick(View v) {
                (,"Control 1:"+.tv_annotation_test1);
            }
        });
        ("Another text");
        (new () {
            @Override
            public void onClick(View v) {
                (,"Control 2:"+.tv_annotation_test2);
            }
        });
    }
}

1. Custom annotations

We've created a new oneJava lib, namedannotationTest, used to host custom annotations, the code is as follows:

@Retention()
@Target()
public @interface BindView {
    int value();
}

2. Annotation processor

Create an additional new oneJava lib, namedprocessorTest, used to carry annotation processing and Java file generation logic.

It mainly includes the following steps:

  • Add annotation processor
  • Annotation processor registration
  • Add java code generation logic

It should be noted that the current annotation processor lib needs to introduce annotation lib -annotationTest, in the current ModuleConfiguration in the file:

    implementation project(':annotationTest')//Rely on the annotation module just created

Annotation processor

The annotation processor code is as follows:

public class BindViewProcessor extends AbstractProcessor {

    private Messager mMessager;
    private Elements mElementUtils;
    private Map<String, ClassCreatorProxy> mProxyMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        (processingEnv);
        mMessager = ();
        mElementUtils = ();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        (());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return ();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        (, "processing...");
        ();
        //Get all comments        Set<? extends Element> elements = ();
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            TypeElement classElement = (TypeElement) ();
            String fullClassName = ().toString();
            ClassCreatorProxy proxy = (fullClassName);
            if (proxy == null) {
                proxy = new ClassCreatorProxy(mElementUtils, classElement);
                (fullClassName, proxy);
            }
            BindView bindAnnotation = ();
            int id = ();
            (id, variableElement);
        }
        //Create a java file by traversing mProxyMap        for (String key : ()) {
            ClassCreatorProxy proxyInfo = (key);
            JavaFile javaFile = ((),proxyInfo.generateJavaCode2()).build();
            try {
                //Generate Java files                (());
            } catch (IOException e) {
                (, " --> create " + () + "error");
            }
        }

        (, "process finish ...");
        return true;
    }
} 

For the sake of simplicity of code demonstration, there is no format verification (such as verification of the type of annotation modifications and other information). If you actually use APT technology, please be sure to verify the usage rules of the annotation.

Annotation processor registration

Custom annotation processors must be registered before they can be used, that is, they need to add automatic and proactive annotations to the annotation processor.

We can use Google autoService to automatically register the annotation processor. First, we need to use the module where the annotation processor is located.File add autoService package import:

    //google autoService
    implementation ":auto-service:1.0-rc4"
    annotationProcessor ":auto-service:1.0-rc4"

Then add the automatically registered annotation to the annotation processor to achieve the automatic registration effect:

@AutoService()
public class BindViewProcessor extends AbstractProcessor {
    ...
}

Java code generation

There are many ways to generate Java code, such as string splicing, JavaPoet, etc.
If you want to use JavaPoet, you need to use the current JavaPoetModuleofIntroduced in the file:

    //javaPoet
    implementation ":javapoet:1.13.0"

The detailed code is as follows:

package ;

import ;
import ;
import ;

import ;
import ;

import ;
import ;
import ;
import ;
import ;

public class ClassCreatorProxy {
    private String mBindingClassName;
    private String mPackageName;
    private TypeElement mTypeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
         = classElement;
        PackageElement packageElement = (mTypeElement);
        String packageName = ().toString();
        String className = ().toString();
         = packageName;
         = className + "_ViewBinding";
    }

    public void putElement(int id, VariableElement element) {
        (id, element);
    }

    /**
      * Create Java code String stitching method
      * @return
      */
    public String generateJavaCode() {
        StringBuilder builder = new StringBuilder();
        ("package ").append(mPackageName).append(";\n\n");
        ("import ..*;\n");
        ('\n');
        ("public class ").append(mBindingClassName);
        (" {\n");

        generateMethods(builder);
        ('\n');
        ("}\n");
        return ();
    }

    /**
      * Add Method string stitching method
      * @param builder
      */
    private void generateMethods(StringBuilder builder) {
        ("public void bind(" + () + " host ) {\n");
        for (int id : ()) {
            VariableElement element = (id);
            String name = ().toString();
            String type = ().toString();
            ("host." + name).append(" = ");
            ("(" + type + ")((()host).findViewById( " + id + "));\n");
        }
        ("  }\n");
    }

    public String getProxyClassFullName()
    {
        return mPackageName + "." + mBindingClassName;
    }

    public TypeElement getTypeElement()
    {
        return mTypeElement;
    }


    /**
      * Create Java code javapoet
      * @return
      */
    public TypeSpec generateJavaCode2() {
        TypeSpec bindingClass = TypeSpec
                //class name setting                .classBuilder(mBindingClassName)
                //The class is public                .addModifiers()
                .addMethod(generateMethods2())
                .build();
        return bindingClass;

    }

    /**
      * Join Method javapoet
      */
    private MethodSpec generateMethods2() {
        ClassName host = (().toString());
         methodBuilder = MethodSpec
                //Method Name                .methodBuilder("bind")
                //The method is public                .addModifiers()
                //Return value                .returns()
                //Method Parameters                .addParameter(host, "host");

        for (int id : ()) {
            VariableElement element = (id);
            String name = ().toString();
            String type = ().toString();
            ("host." + name + " = " + "(" + type + ")((()host).findViewById( " + id + "));\n");
        }
        return ();
    }


    public String getPackageName() {
        return mPackageName;
    }

}

3. External call

After completing the above two steps, the actual work of APT can be run normally. Let’s implement the following calling method to achieve the calling effect of imitating butterknife.

First we need to create a new oneAndroid libModule, namedapt_lib

The current external lib needs to reference the annotation processor lib—processorTest, in the current ModuleThe following configuration is performed in the file:

    implementation project(path: ':processorTest')

Create a new external call method, the code is as follows:

public class BindViewTools {

    public static void bind(Activity activity) {

        Class clazz = ();
        try {
            Class bindViewClass = (() + "_ViewBinding");
            Method method = ("bind", ());
            ((), activity);
        } catch (ClassNotFoundException e) {
            ();
        } catch (IllegalAccessException e) {
            ();
        } catch (InstantiationException e) {
            ();
        } catch (NoSuchMethodException e) {
            ();
        } catch (InvocationTargetException e) {
            ();
        }
    }
}

Its main function is to generate corresponding auxiliary classes for the annotated-bound Activity to achieve the effect of apt being called.

4. Call

After following the steps below, we are actually able to call normally@BindViewThis annotation is here.
We are in our main Module—appCall APT, of course, the APT needs to introduce the previous lib project, which can beappofThe following configuration is performed in the file:

    implementation project(':annotationTest') // Add dependency module    implementation project(':apt_lib') // Add dependency module
    implementation project(':processorTest') // Add dependency module    annotationProcessor project(':processorTest')

It should be noted thatannotationProcessorThis annotation processing tool is used in Java. If you are using the kotlin language, you need to use kapt.

At this time, we directly call the code we call at the beginning, and it can run normally after compilation and execution.

When we view the projectbuildWhen you are in a folder, you can find the files we generated before in the following path:

I saw the code inside as follows:

package ;

public class AnnotationTestActivity_ViewBinding {
  public void bind(AnnotationTestActivity host) {
    host.tvAnnotationTest1 = ()((()host).findViewById( 2131231102));
    host.tvAnnotationTest2 = ()((()host).findViewById( 2131231103));
  }
}

In this way, we complete a simple example of APT.

Summarize

This article mainly provides a brief explanation of APT technology in Android. APT technology can directly generate Java logical code using compiled technology, which is more suitable in scenarios with high code repetition.

This is the article about the usage example of annotated processor APT in Android. For more related content on annotated processor APT in Android, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!