• What is APT? What’s the use?

APT(Annotation Processing Tool) is an Annotation processor that can process annotations at compile time and do things, or generate files at compile time and things like that. Both ButterKnife and EventBus use APT technology, and it is difficult to understand the source code of both frameworks without APT technology.

  • Implementation effect

Add @print annotation to a member variable of any class. You can generate a method dynamically and Print the name of the member variable:

A dynamically generated class might look like this:

  • To compose

  1. First we need to create two JavaLibrary
  2. One for defining annotations and one for scanning annotations
  3. Gets the name of the member variable to which the annotation was added
  4. Dynamically generate classes and methods using IO to generate files
  • In actual combat

  • Create an empty project

  • Create two JavaLibrary

  1. Lib: apt-annotation
  2. Scan the annotated Lib: apt-Processor

  • Once you've created it

  • The APP module relies on two libraries

implementation project(path: ':apt-annotation')
annotationProcessor project(path: ':apt-processor')
Copy the code

  • Create an annotation class in annotation Lib

If you don’t know how to customize annotations, you can go to the first I wrote a Java custom annotations to get started

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface Print {

}
Copy the code

  • Scan annotated Lib to add dependencies

dependencies {
    // Generate meta-inf /... file
    implementation 'com. Google. Auto. Services: auto - service: 1.0 - rc6'
    annotationProcessor 'com. Google. Auto. Services: auto - service: 1.0 - rc6'
    / / rely on apt - the annotation
    implementation project(path: ':apt-annotation')
}
Copy the code

  • Create a class that scans annotations

  • Rewrite init, print Hello,APT

Note: this is JavaLib, so you can’t use Log printing. Instead, you can use Java println() or the annotation handler that gives us the method. We recommend using the annotation handler that gives us the method

  • Witness the miracle

Now that we have done the basic configuration of APT, we can build the project. It’s all on the line

  • Guidelines on pit

  1. If you have successfully output text, APT is configured and ready to proceed
  2. If you fail:
  1. If you don’t find an AbstractProcessor class, you’re not creating a JavaLibrary, so you can delete it and start again
  2. If you click compile and it doesn’t work, try clearing the project before recompiling
  3. If not, check that the previous process’s dependencies are configured correctly
  • Continue to complete the function

Now that we can continue with the above implementation, we need to implement a few methods first

/** * To scan scanned annotations, you can add multiple */
@Override
public Set<String> getSupportedAnnotationTypes(a) {
    HashSet<String> hashSet = new HashSet<>();
    hashSet.add(Print.class.getCanonicalName());
    return hashSet;
}

/** * compiled version, fixed writing can be */
@Override
public SourceVersion getSupportedSourceVersion(a) {
    return processingEnv.getSourceVersion();
}
Copy the code

  • Custom annotation

Let’s start by adding two member variables to the MianActivity and using the annotations we defined

  • Custom annotation

The real place to parse annotations is in the process method, where we first try to get the name of the annotated variable

/** * Scan annotation callback */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    // Get all the member variables that add the Print annotation
    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Print.class);
    for (Element element : elements) {
        // Get the member variable name
        Name simpleName = element.getSimpleName();
        // Prints the member variable name
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,simpleName);
    }
    return false;
}
Copy the code

  • Let's compile it

  • The generated classes

Now that we can get the name of the annotated variable, we can simply create a utility class using a string and write it to the local IO stream

  • See the effect

Now click compile, and we can see that the build file under the APP module already has our generated classes

  • A method is called

Now we go back to MainActivity and can call the dynamically generated class directly

  • End of actual combat

Is it over… EventBus is a class that has been written in the same way as EventBus. The EventBus is a class that has been written in the same way as EventBus.

See big guy is such splicing, this I rest assured 🤡, we look againThe source code of ButterKnifeHow is it made:

The source code of ButterKnifeNot a string concatenation!! Vaguely seeTypeSpec.classBuilderWhat the hell is this? However, as experienced programmers, we can easily find the answer to this question

  • JavaPoet

After an hour of searching for JavaPoet, it seems that JavaPoet can help us generate classes with object-oriented thinking, so that we don’t have to manually concatenate strings to generate classes. Let’s optimize the code above:

Add first depend on implementation 'com. Squareup: javapoet: 1.13.0'Copy the code

/** * Scan annotation callback */
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    // Get all the member variables that add the Print annotation
    Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Print.class);

    / / generated class
    TypeSpec.Builder classBuilder = TypeSpec
            .classBuilder("PrintUtil")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL);

    for (Element element : elements) {
        // Get the member variable name
        Name simpleName = element.getSimpleName();
        // Generate method
        MethodSpec method = MethodSpec.methodBuilder("print$$"+simpleName)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(void.class)
                .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
                .build();
        classBuilder.addMethod(method);
    }
    / / package
    JavaFile javaFile = JavaFile
            .builder("com.lkx.helloapt", classBuilder.build())
            .build();
    try {
        javaFile.writeTo(processingEnv.getFiler());
    } catch (IOException e) {
        e.printStackTrace();
    }
    return false;
}
Copy the code

  • Compile the

  • conclusion

  1. APT can scan annotations at the compiler to help us generate classes ahead of time
  2. JavaPoet allows us to gracefully generate classes without concatenating them
  3. The main function of APT is to replace some functions of reflection and avoid performance degradation
  4. APT only affects speed a little bit at compile time, but not at run time, while reflection does the opposite