background

Compile-time annotations are increasingly being used in open source frameworks such as

JakeWharton/butterknife view

Greenrobot/EventBus event

Square /dagger dependency injection

Libraries like these are becoming more and more common in development and work, and they are designed to help developers develop quickly and save time while maintaining efficiency. Both use the idea of compile-time annotations.

Because of this popularity, it’s important to learn how it works so you can solve compile-time annotation problems and apply the technique to your own open source libraries

thought

Compile-time annotation frameworks are written in a relatively fixed format, such as subcontracting

Write the picture description here

The format is relatively fixed, but it can be flexible, such as combining API and Annotations in a moudel

The dependencies in Moudel are also very fixed

The Processors dependency package has apI-Annotations

App depends on packages like API-Annotations – Processors

Except the App is an Android moudel, all the others are Java moudel

annotationsannotations

Before I go into annotations, I want to have a general understanding of Java and Android annotations, which I wrote in my previous blog

Java- Full explanation of annotations

Android- Notes in detail

Initialize a HelloWordAtion annotation with a Target of elementType. TYPE that decorates the class object

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface HelloWordAtion {
    String value(a);
}Copy the code

Typically, an annotation needs to correspond to an annotation handler, which is handled in Processors

processorsAnnotation processor

The annotation handler needs to inherit AbstractProcessor classes and override the following four methods:

init

Init (ProcessingEnvironment processingEnv) is called by the annotation processing tool

process

process(Set
Annotations, RoundEnvironment roundEnv) This is the equivalent of each processor’s main function, main(), where you write your code that scans, evaluates, and handles annotations, as well as generating Java files.

getSupportedAnnotationTypes

EtSupportedAnnotationTypes () must be specified here, the annotation processor which is registered to the annotation. Notice that its return value is a collection of strings containing the legal full name of the annotation type that this processor wants to process

A collection of annotation types supported by the @return annotator, or an empty collection if no such type exists

getSupportedSourceVersion

Specified using Java version, usually returns SourceVersion. Here latestSupported (), the default return SourceVersion. RELEASE_6 `

The Java version used by @return

Generate annotation handler

Having an in-depth understanding of AbstractProcessor, I know the method of coding during initial compilation of the core and process. In process, we write the code by getting the data passed. Here, we simply output the information by printing. More on how to implement butterKnife on your own

public class HelloWordProcessor extends AbstractProcessor {

    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // Filer is an interface that supports the creation of new files through annotation handlers
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(HelloWordAtion.class)) {

            if(! (elementinstanceof TypeElement)) {
                return false;
            }

            TypeElement typeElement = (TypeElement) element;
            String clsNmae = typeElement.getSimpleName().toString();
            String msg = typeElement.getAnnotation(HelloWordAtion.class).value();

            System.out.println("clsName--->"+clsNmae+" msg->"+msg);
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes(a) {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(HelloWordAtion.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion(a) {
        returnSourceVersion.latestSupported(); }}Copy the code

At this stage, the annotation processor corresponding to HelloWordAtion has been written, which simply prints the class of the HelloWordAtion annotation and the value information specified by the annotation

When the preparation is complete, the app triggers the call

@HelloWordAtion("hello")
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}Copy the code

Annotate the annotation class MainActivity and specify value as Hello. If you compile or run the project directly, you will not see any output. The next step is to specify the annotation handler.

  • Create a resources folder in the main directory of the Processors library.

  • Create META-INF/services directory in resources folder.

  • 3, in the meta-inf/services directory folder to create javax.mail.. The annotation process. The Processors files;

  • 4, the javax.mail. The annotation. Process. Processors file is written to the full name of annotation processor, including package path;

After going through the above steps, it can run successfully, but it is too complicated and the bloggers have been working for a long time to configure this step, so we recommend using the open source framework AutoService

AutoService

AutoService

Rely directly on Processors

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

use

@AutoService(Processor.class)
public class HelloWordProcessor extends AbstractProcessor {
xxxxxxx
}Copy the code

Run the program here and successfully see the output information in the background

Write the picture description here

You need to switch to the Gradle Console window in the lower right corner. If the mutation is not successful, you can clean the project and restart it later

The next step, of course, is to write the data to the Java Class

Filer

A Filer is initialized in AbstractProcessor’s init method

 private Filer filer;  

    @Override  
    public synchronized void init(ProcessingEnvironment processingEnv) {  
        super.init(processingEnv);    
        filer = processingEnv.getFiler();  
    }Copy the code

At this point we have the helper classes for writing classes, but we still need the code generation logic. Here we use Javapoet

javapoet

JavaPoet is an auxiliary library for creating.java source files. It is very convenient to help you generate.java source files. GitHub has a very detailed usage

javapoet

Processors depend on:

compile 'com. Squareup: javapoet: 1.8.0 comes with'Copy the code

Combine the above techniques and generate the following code, modeled after Javapoet’s first Example

 @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element element : roundEnv.getElementsAnnotatedWith(HelloWordAtion.class)) {

            if(! (elementinstanceof TypeElement)) {
                return false;
            }

            TypeElement typeElement = (TypeElement) element;
            String clsNmae = typeElement.getSimpleName().toString();
            String msg = typeElement.getAnnotation(HelloWordAtion.class).value();

            System.out.println("clsName--->"+clsNmae+" msg->"+msg);

            Create the main method
            MethodSpec main = MethodSpec.methodBuilder("main")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(String[].class, "args")
                    .addStatement("$T.out.println($S)", System.class, clsNmae+"-"+msg)
                    .build();

            Create the HelloWorld class
            TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(main)
                    .build();

            try {
                / / generated com. Wzgiceman. Viewinjector. The HelloWorld. Java
                JavaFile javaFile = JavaFile.builder("com.wzgiceman.viewinjector", helloWorld)
                        .addFileComment(" This codes are generated automatically. Do not modify!")
                        .build();
                // Generate the file
                javaFile.writeTo(filer);
            } catch(IOException e) { e.printStackTrace(); }}return true;
    }Copy the code

We will focus on the process method, which is the body of the method that writes the code. We will change the output information to the HelloWordAtion annotation based on javapoet’s Example. You can see the auto-compiled HelloWorld class in the following path

Write the picture description here

Simple compile-time annotations are done here, but the automatic writing of compile-time annotations can also cause code confusion, which can lead to file conflicts during multiple build compilations, so we need to introduce Android-apt here

android-apt

Android-apt can rely on the annotation processor at compile time and work, but does not contain any leftover useless files when generating APK. It assists the corresponding directory of The Android Studio project to store the files generated by the annotation processor during compilation

android-apt

Dependent use:

The root directory build. Gradle

 classpath 'com. Neenbedankt. Gradle. Plugins: android - apt: 1.8'Copy the code

In the app

apply plugin: 'com.neenbedankt.android-apt'

apt project(':processors')Copy the code

Here’s apt instead compile depends on processors

conclusion

The simple compile-time annotations are done, but the API module is not covered, so don’t worry about the extension in the next blog, using the master compile-time annotations and the butterknife framework, the mainstream of the current set of custom injection framework will be explained in detail API module use. You’ll find that ButterKnife is very simple, of course, and can spread freely, expanding back into any of your open source projects, replacing reflection for efficiency. If you can’t wait, go to GitHub and download the source code.


column

Annotations – Compile runtime annotations


The source code

Download the source code


advice

If you have any questions or suggestions, please join the QQ group and tell me!