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