Android component design 2 - Custom annotation processor (APT)

Put the picture up first

This is Android component design - the magic of Gradle In the componentization architecture diagram in, component 1... Component 4 cannot be interdependent before. If component 1 wants to call component 2, it can only pass the routing capability of the basic layer components.

If the components are not dependent on each other before, they can be called through the method of class loading. For example, if the app shell wants to call the registration module, it can get the full class name of the RegisterActivity of the registration module and start it through class loading

fun jump(view: View) {
	//com.study.register.RegisterActivity
	//
    val RegisterActivity = Class.forName("com.study.register.RegisterActivity")

    startActivity(Intent(this,RegisterActivity))
}

Alternatively, you can register the class information of all activities in the global Map by registering the routing table. If there are hundreds of activities, you need to register each Activity manually.

Is there a way to get all the class information during compilation and register it in the routing table to avoid manual registration

APT is an annotation processing tool, which is used to find annotations in source code during compilation and generate new class functions according to annotations; For example, Eventus, ButterKnife and ARouter generate new classes during compilation. For example, EventBus uses the traditional code generation method to write strings directly

Traditional way

//1 write the package name first and write directly
package com.study.modulelization
//2 write the class name again
class HttpConfig {
	//3 if there is a method, write the method name again
    companion object{

        private const val DEBUG = "http://106.108.2.3//test-baidu.com"
        private const val RELEASE = "http://106.108.2.3//baidu.com"
    }
}

For example, ButterKnife and ARouter adopt the javapool method and introduce the idea of object-oriented. Contrary to the traditional method, it is not as readable as the traditional method

1. Define route annotation

@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.TYPE)
annotation class LayRouter(
    val group:String,
    val path:String = ""
)

Annotations in Kotlin are similar to those in Java. Meta annotations need to be declared when customizing annotations;

Where Retention represents the period when the annotation exists, SOURCE (SOURCE code period), BINARY (compilation period, distinguished from Java, Java compilation period is Class) and RUNTIME (RUNTIME)

Target is the declared object, TYPE (class, interface, etc.), FUNCTION (FUNCTION, but excluding construction method), CONSTRUCTOR (construction method), PROPERTY_GETTER / PROPERTY_SETTER (get set method), FIELD (property)

2 annotation processor APT Compiler

After the annotation is declared, the annotation processor is required to process the annotation. During system compilation, the user-defined annotation will be found from all the code to generate the code. Then the annotation processor is equivalent to a service running in the background and detecting the code. Therefore, some configuration needs to be done

layout_compiler # build.gradle

dependencies {
	//Service behind
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
	//JavaPoet
    implementation "com.squareup:javapoet:1.13.0"
	//Annotations to process
    implementation project(path: ':lay_router')
    
}

Google's AutoService can detect the code in the background at compile time and find custom annotations

3 initialization configuration before annotation processing

@AutoService(Processor::class)
class AnnotationProcessor : AbstractProcessor() {

    //Tool class for operating Element (class, function, property, etc.)
    private var elementTool:Elements? = null
    //Class information operation, class inheritance, etc
    private var typeTool:Types? = null
    //Log printing
    private var message:Messager? = null
    //File output
    private var filer:Filer? = null
	//The supported annotation types are our customized annotations
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return mutableSetOf(LayRouter::class.java.canonicalName)
    }
	//Supported Java version 1.8
    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.RELEASE_8
    }
	//The supported parameter options are the parameters passed from the app shell to the annotation processor
    override fun getSupportedOptions(): MutableSet<String> {
        return mutableSetOf("moduleName")
    }

    override fun init(processingEnv: ProcessingEnvironment?) {
        super.init(processingEnv)

        elementTool = processingEnv!!.elementUtils
        typeTool = processingEnv.typeUtils
        message = processingEnv.messager
        filer = processingEnv.filer

        message!!.printMessage(Diagnostic.Kind.NOTE,"init Annotation processor initialization")

        val s = processingEnv.options["moduleName"]
        message!!.printMessage(Diagnostic.Kind.NOTE,"--------->$s")
    }


    //Processing annotations
    override fun process(p0: MutableSet<out TypeElement>?, p1: RoundEnvironment?): Boolean {
    
        return false
    }
}

AbstractProcessor is an annotation processor. A special chapter will be introduced later to cooperate with the compilation process.

After a class is modified by annotations, the AnnotationProcessor needs to get the information of this class to facilitate code generation. Therefore, it needs to create some tools that can obtain class information. The tools we want exist in the processing environment

public interface ProcessingEnvironment {
    Map<String, String> getOptions();
	//Log printing
    Messager getMessager();
	//File generation
    Filer getFiler();
	//The operation of class interface attributes can be regarded as an element
    Elements getElementUtils();
	//Operation of class information
    Types getTypeUtils();
	//Get version information
    SourceVersion getSourceVersion();

    Locale getLocale();
}

In the app Shell project, annotations are used. How can the lay_compiler get the parameter information of the app Shell project? Then, in the app shell, pass the parameters to the annotation processor through Gradle

Now it's Kotlin's project. When passing parameters, pass parameters through kapt - arguments - arg

 kapt {
    arguments{
        arg("moduleName","zhijiedamamamfamfafafafa")
    }
}

AnnotationProcessor # init

override fun init(processingEnv: ProcessingEnvironment?) {
    super.init(processingEnv)

    elementTool = processingEnv!!.elementUtils
    typeTool = processingEnv.typeUtils
    message = processingEnv.messager
    filer = processingEnv.filer

    message!!.printMessage(Diagnostic.Kind.NOTE,"init Annotation processor initialization")

    val s = processingEnv.options["moduleName"]
    message!!.printMessage(Diagnostic.Kind.NOTE,"--------->$s")
}

Get options through ProcessingEnvironment, get value through key, and print parameters from message

In this way, the initialization of the annotation processor is completed. If you want to use our customized annotation processor in the app shell, you need to rely on it

app shell # build gradle

dependencies {

    dependency.each { k, v -> implementation v }

    if (rootProject.isRelease) {
        //Dependent package
        implementation project(path: ':register')
        implementation project(path: ':lay_router')
		//kotlin uses kapt dependent annotation processor,
		//Java uses annotation processor
        kapt project(path: ':lay_compiler')
    }

    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

Stepping on the pit: the annotation processor created is not working

Do you think this will be done, mistake ❎, There is a hole here. After the guided package depends, no matter how compiled, it cannot enter the initialization method

After the initialization of the annotation processor is completed, create javax under resource meta inf services in the main folder annotation. processing. Processor file, which needs to declare the annotation processor we created, is equivalent to registration

This pit here has really been trampled all afternoon

Tags: Java Android Gradle Annotation apt

Posted by notionlogic on Mon, 24 Jan 2022 20:27:50 +1030