Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Time:2020-1-21

Blog Homepage

Reference resources:

  • Gradle core (5): gradle advanced customization

Gradle component configuration

The configuration of Android component development is inseparable from the gradle build tool, which makes the project possible. The core of gradle is based on the groovy script language, which is based on Java and extends Java, so gradle needs to rely on JDK and groovy library.

A brief introduction to gradle

Gradle syntax: from the log output of gradle to explain the journey of component-based design

//The first way to print a string
println("hello, gradle!")

//The second way to print strings
println "hello2, gradle!"

It can be seen from the two string output methods that the method can not write brackets and semicolons after a sentence, which is a feature of groovy.

It can be used in the build.gradle file of Android projectprintlnFunction output log. Then passBuild->Toggle viewView the log of build output

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Custom properties

Gradle can add additional custom attributes through ext attributes. Create a new one firstconfig.gradleFile and custom isrelease attributes for dynamic switching: componentization mode / integration mode

ext {
    //False: componentization mode (submodules can run independently)
    //True: integration mode (package the whole project APK, sub modules cannot run independently)
    isRelease = true
}

How to use the config file? You need to pass theapply fromMethod reference config.gradle file

// build.gradle

//Can be referenced through apply from
apply from: 'config.gradle'

Then use the custom attribute in the build.gradle file of APP application project

// build.gradle

//Using custom attributes, attributes need to be written in ${}
println "${rootProject.ext.isRelease}"

Config.gradle file basic configuration

Create a new shopping shop module. The project structure is as follows:
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot
Check the build.gradle file in the app Application module and the shop library module. It is found that the red box configuration in the figure above is very similar. Can you use itSub moduleHow about configuration? Use the gradle custom attribute to extract the common configuration and put it in a separate file for other builds to reference? The answer is yes.

Next, the configuration of the red box is related to the Android version. You can define a map collection to store related attribute information. The configuration is as follows:

// config.gradle

ext {
    //Define map to access version related information, and the key name can be arbitrary
    versions = [
            compileSdkVersion: 29,
            buildToolsVersion: "29.0.2",
            minSdkVersion    : 21,
            targetSdkVersion : 29,
            versionCode      : 1,
            versionName      : "1.0"
    ]
}

Then access the attributes defined in the map collection in the build.gradle file of the app Application module and the shop library module, for example, access the customized attributes in the map in the build.gradle file of the app Application module

// get Map
def versions = rootProject.ext.versions

android {
    //Access values directly through map.key
    compileSdkVersion versions.compileSdkVersion
    buildToolsVersion versions.buildToolsVersion
    defaultConfig {
        applicationId "com.example.modular.todo"
        minSdkVersion versions.minSdkVersion
        targetSdkVersion versions.targetSdkVersion
        versionCode versions.versionCode
        versionName versions.versionName
    }
}

1. Applicationid configuration
Switch between the component module and the integrated module. In order to run independently, the component module needs to be set when switching the library module to the application moduleapplicationId

Therefore, you need to configure different applicationid properties

// config.gradle

ext {
    //When switching between componentization and integration, set different applicationids
    appId = [
            app : "com.example.modular.todo",
            shop: "com.example.modular.shop"
    ]
}

Use in build.gradle file of APP Application module

def appId = rootProject.ext.appId
android {
    defaultConfig {
        applicationId appId.app
    }
}

2. Switch between production and formal environment configuration in the code
Sometimes you need to switch between production and formal environment configuration in the code, such as the URL of network request. Android provides us with a custom buildconfig feature.

// config.gradle

ext {
    baseUrl = [
            Debug: "https://127.0.0.1/debug", // test version URL
            Release: "https://127.0.0.1/release" // official version URL
    ]
}

In the app Application module build.gradle filebuildConfigFieldConfiguration, which can be accessed in the code through buildconfig.baseurl.

// build.gradle

def baseUrl = rootProject.ext.baseUrl
android {
    buildTypes {
        debug {
            // void buildConfigField(
            //            @NonNull String type,
            //            @NonNull String name,
            //            @NonNull String value) { }
            buildConfigField("String", "baseUrl", "\"${baseUrl.debug}\"")
        }

        release {
            buildConfigField("String", "baseUrl", "\"${baseUrl.release}\"")
        }
    }
}

3. Dependencies dependency configuration

// config.gradle

ext {
    appcompatVersion = "1.0.2"
    constraintlayoutVersion = "1.1.3"
    dependencies = [
            appcompat       : "androidx.appcompat:appcompat:${appcompatVersion}",
            constraintlayout: "androidx.constraintlayout:constraintlayout:${constraintlayoutVersion}",
    ]

    tests = [
            "junit"        : "junit:junit:4.12",
            "espresso"     : "androidx.test.espresso:espresso-core:3.1.1",
            "androidJunit": "androidx.test.ext:junit:1.1.0"
    ]
}

Build.gradle file reference in app module

// build.gradle

def supports = rootProject.ext.dependencies
def tests = rootProject.ext.tests

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //Standard writing
    // implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.2'
//    implementation supports.appcompat
//    implementation supports.constraintlayout

    //Supports dependency
    supports.each { key, value -> implementation value }

    testImplementation tests.junit
    androidTestImplementation tests.espresso
    androidTestImplementation tests.androidJunit
}

4. Signature configuration
Note when signing configuration:signingConfigsMust be written inbuildTypesbefore

android {
    //Signature configuration (hidden pit: must be written before buildtypes)
    signingConfigs {
        debug {
            //Tiankeng: it's filled in incorrectly. We can't find the problem if the compilation fails
            storeFile file('/Users/xujinbing839/.android/debug.keystore')
            storePassword "android"
            keyAlias "androiddebugkey"
            keyPassword "android"
        }
        release {
            //Signing certificate file
            storeFile file('/Users/xujinbing839/work/mycode/todo/todo_modular/keystore/modular')            
            Storetype "modular" // type of signing certificate            
            storePassword "123456" //Signing certificate file的密码        
            Keyalias "modular" // key alias in signing certificate  
            Keypassword "123456" // the password of the key in the signing certificate
            V2signingenabled true // enable V2 packaging
        }
    }

    buildTypes {
        debug {
            //Set signature information on build type
            signingConfig signingConfigs.debug
        }

        release {
            //Set signature information on build type
            signingConfig signingConfigs.release
        }
    }
}

Other configurations

android {
    defaultConfig {
        //Open subcontracting
        multiDexEnabled true
        //Set subcontract configuration
        // multiDexKeepFile file('multidex-config.txt')

        //Generate the PNG picture of the specified dimension from the SVG picture
        // vectorDrawables.generatedDensities('xhdpi','xxhdpi')
        //Use support-v7 compatible (version 5.0 and above)
        vectorDrawables.useSupportLibrary = true
        //Keep only specified and default resources
        resConfigs('zh-rCN')

        //Configure so library CPU architecture (real machine: arm, simulator: x86)
        // x86  x86_64  mips  mips64
        ndk {
            //abiFilters('armeabi', 'armeabi-v7a')
            //Start for simulator
            abiFilters('x86', 'x86_64')
        }
    }

    //Adboptions can add configuration to ADB operation options
    adbOptions {
        //Configure operation timeout in milliseconds
        timeOutInMs = 5 * 1000_0

        //Option configuration of ADB install command
        installOptions '-r', '-s'
    }
    //The configuration of DX operation accepts a closure of type dexoptions, and the configuration is provided by dexoptions
    dexOptions {
        //Configure the maximum heap memory allocated for DX command execution
        javaMaxHeapSize "4g"
        //Configure whether to execute the DEX Libraries Project in advance. When enabled, the incremental build speed will be increased, but the clean build speed will be affected. True by default
        preDexLibraries = false
        //Configure whether to enable Jumbo mode. The code method is to force it to be enabled when it is over 65535 to build successfully
        jumboMode true
        //Configure the number of threads gradle uses to run the DX command
        threadCount 8
        //Configure multidex parameters
        additionalParameters = [
                '-- multi DEX', // multi DEX subcontracting
                '-- set Max IDX number = 50000', // maximum number of methods in each package
                //'-- main DEX list =' + '/ multidex config. TXT', // package to the file list of main classes.dex
                '--minimal-main-dex'
        ]
    }
    //Execute the gradle lint command to run the lint check. The default generated report is in outputs / lint results.html
    lintOptions {
        //If a lint check error is encountered, the build will be terminated, which is generally set to false
        abortOnError false
        //Treat warnings as errors (old version: warning as errors)
        warningsAsErrors false
        //Check new API
        check 'NewApi'
    }
}

Detailed deployment of componentization

The significance of component development

What is component development?
Component development is to divide an app into multiple modules. Each module is a module. In the process of development, we can make these components depend on each other or debug some components separately. But when we finally publish, we will merge these components into an APK, which is component development.

Componentization and plug-in development are slightly different:
During plug-in development, the whole app is divided into many modules, including a host and multiple plug-ins. Each module is an APK (each component module is a lib). At last, the host APK and plug-in APK are packaged separately.

Why componentization?
1. Development requirements
Independent, interactive, arbitrary combination, highly decoupled
2. Team efficiency
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Difference and switch between Library and Application

Differences between phone module and Android Library:

The phone module can be run independently, compiled into an APK, and the applicationid needs to be configured in the build.gradle file; the Android library cannot be run independently, compiled into an APK, and the applicationid does not need to be configured in the build.gradle file.
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Switch between phone module and Android Library:

Take the sub module shopping shop as an example:

If the shopping shop module can compile the APK independently, you need to switch to the phone module (that is, the componentization mode), and dynamically switch through isrelease:

  1. When isrelease is true: integration mode (i.e. the whole project APK can be packaged), the sub module shopping shop cannot run independently
  2. When isrelease is false: component mode, the sub module shopping shop can run independently

Where isrelease variable is a property defined in config.gradle

// build.gradle

If (isrelease) {// if it is a release version, each module cannot run independently
    apply plugin: 'com.android.library'
} else {
    apply plugin: 'com.android.application'
}

android {
    defaultConfig {
        //If it is an integrated mode, it cannot have applicationid
        if (!isRelease) applicationId appId.shop
    }
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

When the sub module shopping shop switches from the library module to the application module, you may need to write test code, such as the starting entry.

UsesourceSetsConfiguration: put the test code into the debug folder. When you switch to integration mode, when you package it into APK, remove all the debug code (that is, the debug code will not be packaged into APK).

android {
    //Configure the resource path, facilitate the test environment, and package it without integration into the formal environment
    sourceSets {
        main {
            if (!isRelease) {
                //If it is a component mode, it needs to be run separately
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                //Integrated mode, the whole project package apk
                manifest.srcFile 'src/main/AndroidManifest.xml'
                java {
                    //When release, the files in the debug directory do not need to be merged into the main project
                    exclude '**/debug/**'
                }
            }
        }
    }
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Component development specification:

  1. Sub module Shopping Shop: add prefix to SRC class and res resource command, shop, for example: Shop > activity > Home
  2. App module: can not be modified, default

Interaction between module and module

How do modules interact (including jump, parameter transfer, etc.)? There are many ways:

  1. EventBusVery confusing, difficult to maintain
  2. reflexReflection technology can succeed, but the maintenance cost is high, and there is a high version of @ hide limitation
  3. Implicit intentionThe maintenance cost is good, but it’s troublesome. You need to maintain the action of the manifest
  4. BroadCastReceiverDynamic registration is required (after 7.0), and the employer sends broadcast
  5. Class loadingIt needs accurate full class name path, high maintenance cost and prone to human error

The first implementation: class loading technology interaction

To load the target class through the forname of class, you need to know exactly the full class name path of the target class.

private void jump() {
    try {
        Class<?> targetClass = Class.forName("com.example.modular.shop.ShopActivity");

        Intent intent = new Intent(this, targetClass);
        intent.putExtra("moduleName","app");
        startActivity(intent);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

The second implementation: global map records path information

To jump, if you know the class object of the target class, you can’t jump. Next, you just need to solve the class object search of the target class.

You can define a pathbean to encapsulate information about the target class, such as the full pathname of the target class and the class object of the target class

public class PathBean {

    Public string path; // jump to the full class name of the target
    Public class <? > targetclass; // jump to the class object of the target class

    public PathBean(String path, Class<?> targetClass) {
        this.path = path;
        this.targetClass = targetClass;
    }
}

There will be many pathbeans in a module, which can be accessed in a list, but there are many modules, which can be distinguished by using map.

//Key: module name, such as shop module value: all activity path information under the module
 private static final Map<String, List<PathBean>> mGroupMap = new HashMap<>();

Mgroupmap is a map, key is the module name, such as: Shop module; value is all the activity path information under the module

Add all path information to mgroupmap, and obtain the target class object according to the group name (module name) and path name.

/**
 *Add path information to the global map
 */
public static void addGroup(String groupName, String path, Class<?> targetClass) {
    List<PathBean> pathBeans = mGroupMap.get(groupName);
    if (pathBeans == null) {
        pathBeans = new ArrayList<>();
        pathBeans.add(new PathBean(path, targetClass));
        mGroupMap.put(groupName, pathBeans);
    } else {
        pathBeans.add(new PathBean(path, targetClass));
    }
}

/**
 *Get target class based on group name and pathname
 */
public static Class<?> findTargetClass(String groupName,String path) {
    List<PathBean> pathBeans = mGroupMap.get(groupName);
    if (pathBeans != null) {
        for (PathBean pathBean : pathBeans) {
            if (!TextUtils.isEmpty(path) && path.equalsIgnoreCase(pathBean.path)) {
                return pathBean.targetClass;
            }
        }
    }
    return null;
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Apt introduction and use

APT introduction

Apt (annotation processing tools) is a tool for processing annotation. It detects the annotation in the source code file and uses annotation for additional processing. When processing annotation, annotation processor can generate additional source files and other files according to the annotation in the source file (the specific content of the file is determined by the author of annotation processor). Apt will compile the generated source files and the original source files, and generate class files together.

Popular understanding: according to the rules, help us generate code and class files.

1. Core implementation principle of APT

The basic principle of annotation parsing at compile time is to add annotations to some code elements (such as types, functions, fields, etc.), and the javac compiler will check the subclass of abstractprocessor at compile time, call the process function of this type, and then pass all the annotated elements to the process function, so that the developers can carry out corresponding operations in the compiler For example, generate new Java classes according to annotations, which is the basic principle of open source libraries such as arouter, butterknife, dragger, etc.

2. Class file of Java source file programming layer

The tool is through the javac tool, and the annotation processor is an annotation tool in javac, which is used for compile time scanning and processing. Think of it as a specific annotation. Register your own annotation processor.

3. How to register annotation processor
Myprocessor to javac. You must provide a. Jar file. Like other. Jar files, you package your annotation processor into this file. In addition, in your jar, you need to package a specific file javax.annotation.processing.processor to the meta-inf / services path.

Detailed explanation of knowledge points

1. jar

  • Com.google.auto.service: auto service the Java generated source code base provided by Google
  • Com.squareup: Java applet provides various APIs for you to generate java code files with various postures

2. @AutoService

This is an annotation introduced in another annotation handler. The autoservice annotation processor is developed by Google to generate meta-inf / services / javax.annotation.processing.processor files. We can use annotations in the annotation processor. Very convenient
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

3. Structural language

For Java source files, it is also a kind of structural language. The JDK provides an interface to describe the language elements of the structure.
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

During annotation processing, we scan all Java source files. Each part of the source code is an element of a specific type. In other words: an element represents an element of a program, such as a package, class, or method. Each element represents a static, language level component.

package com.example; // PackageElement

public class Foo { // TypeElement
    
   private int a; // VariableElement
   private Foo other; // VariableElement

   public Foo {} // ExecutableElement

   public void setA( // ExecutableElement
      int newA // VariableElement ) {}
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Element program element

Packageelement represents a package element.

Typeelement represents a class or interface program element.

Executableelement represents a method, constructor, or initializer (static or instance) of a class or interface, including annotation type elements.

Variableelement represents a field, enumeration constant, method or constructor parameter, local variable, resource variable or exception parameter.

Typeparameterelement represents the formal type parameter of a generic class, interface, method, or constructor element.

Types
A tool class for typemirror

Filer
With filer you can create java files

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Abstractprocessor core API

1. process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)

The main function of each processor is main(). Here you can write your code for scanning, evaluating, and processing annotations, as well as generating java files. Enter the roundenvironment parameter, which allows you to query the annotated elements that contain specific annotations.

2. getSupportedAnnotationTypes()

Which annotation is registered with this annotation processor. Note that its return value is a collection of strings containing the legal full name of the annotation type the processor wants to process. In other words, here you define which annotations your annotation processor registers with.

3. getSupportedSourceVersion()

Used to specify the Java version you are using. Typically, sourceversion. Latestsupported() is returned here. If you have a good reason to support only java6, you can also return sourceversion. Release 6

4. getSupportedOptions()

Specifies the option parameters to be processed by the annotation processor. Option parameter values need to be configured in the gradle file

//Configure option parameter value in gradle file (for apt parameter transmission and reception)
//Remember: it must be written under the defaultconfig node
javaCompileOptions {
    annotationProcessorOptions {
        arguments = [moduleName: project.getName()]
    }
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

These APIs can also be specified using annotations:

//Autoservice is a fixed way of writing. Just add an annotation
//Through @ auto service in auto service, an autoservice annotation processor can be automatically generated for registration
//Used to generate meta-inf / services / javax.annotation.processing.processor file
@AutoService(Processor.class)
//Allowed / supported annotation types for annotation processor to handle (new annotation module)
@SupportedAnnotationTypes({"com.example.modular.annotations.ARouter"})
//Specify JDK compiled version
@SupportedSourceVersion(SourceVersion.RELEASE_7)
//Parameters received by annotation processor
@SupportedOptions("moduleName")
public class ARouterProcessor extends AbstractProcessor {
  // ignore
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Processing environment core API

// ignore
public class ARouterProcessor extends AbstractProcessor {
   //Operation element tool class (class, function, property are element)
    private Elements elementUtils;

    //Type (class information) tool class, containing the tool methods used to operate typemirror
    private Types typeUtils;

    //Messager is used to report errors, warnings and other prompt messages
    private Messager messager;

    //File generator class / resource. Filter is used to create new source files, class files and auxiliary files
    private Filer filer;

    //Module name, get build.gradle from getoptions and pass it on
    private String moduleName;

    //This method is mainly used for some initialization operations. Through the parameter processingenvironment of this method, some useful tool classes can be obtained
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //Processingenv is a protected property of the parent class, which can be used directly.
        //In fact, it is the parameter processingenvironment of init method
        //Processingenv. Getmessager(); // refer to line 64 of source code
        elementUtils = processingEnvironment.getElementUtils();
        messager = processingEnvironment.getMessager();
        filer = processingEnvironment.getFiler();
        typeUtils = processingEnvironment.getTypeUtils();

       //Get the parameters from build.gradle through processingenvironment
        Map<String, String> options = processingEnvironment.getOptions();
        if (options != null && !options.isEmpty()) {
            moduleName = options.get("moduleName");
            //There is a hole: diagnostic.kind.error. The abnormality will end automatically. It's not as easy to use as Android log. E
            messager.printMessage(Diagnostic.Kind.NOTE, "moduleName=" + moduleName);
        }
    }
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Roundenvironment core API

// ignore
public class ARouterProcessor extends AbstractProcessor {
      /**
     *Equivalent to main function, start processing annotation
     *The core method of annotation processor, dealing with specific annotations and generating java files
     *
     *@ param set uses the node set that supports annotation processing (annotation is written on the class)
     *The current or previous running environment of @ param roundenvironment, through which you can find the annotations.
     *@ return true means that the subsequent processor will not process it (it has finished processing)
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set.isEmpty()) return false;

        //Get all class nodes with arouter annotation
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
        //Traverse all class nodes
        for (Element element : elements) {
            // ignore
        }
        // ignore
        return true;
    }
}

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Element core API

Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

APT use

Compatibility of development environment

https://github.com/google/auto

1. Android studio 3.2.1 + gradle 4.10.1 critical version

dependencies {
     //Register the annotation and generate the configuration information of meta-inf. RC2 has a pit after gradle 5.0
     // As-3.2.1 + gradle4.10.1-all + auto-service:1.0-rc2
     implementation 'com.google.auto.service:auto-service:1.0-rc2'
}

2. Android studio 3.4.1 + gradle 5.1.1 downward compatibility

dependencies {
    // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
}

Using APT Technology to help us generate code

1. Arouter notes

Create a new Java library project named annotations. Then createARouterannotation

/**
 * <ul>
 *< li > @ target (ElementType. Type) // interface, class, enumeration, annotation</li>
 *< li > @ target (ElementType. Field) // property, enumerated constant</li>
 *< li > @ target (ElementType. Method) // method</li>
 *< li > @ target (ElementType. Parameter) // method parameter</li>
 *< li > @ target (ElementType. Constructor) // constructor</li>
 *< li > @ target (ElementType. Local? Variable) // local variable</li>
 *< li > @ target (ElementType. Annotation? Type) // this annotation is used on another annotation</li>
 *< li > @ target (ElementType. Package) // package</li>
 *< li > @ retention (retentionpolicy. Runtime) < br > the annotation will exist in the class bytecode file. The content of the annotation can be obtained by reflection when the JVM is loaded</li>
 * </ul>
 *
 *Life cycle: source < class < runtime
 *1. In general, if you need to dynamically obtain annotation information at runtime, use runtime to annotate
 *2. Some preprocessing operations should be performed at compile time, such as butterknife, annotated with class. Annotations exist in the class file, but are discarded at run time
 *3. Do some checking operations, such as @ override, annotate with source code. Annotation only exists at source level, which is discarded during compilation
 */
@Target (ElementType. Type) // the annotation acts on the class
@Retention (retentionpolicy. Class) // some preprocessing operations are required at compile time. The annotation will exist in the class file
public @interface ARouter {

    //Detailed route path (required), such as: "app / mainactivity"
    String path();

    //Routing group name (optional, if not filled in, it can be intercepted from path)
    String group() default "";
}

2. User defined arouterprocessor annotation processor
Create a new Java library project named compiler. Create the arouterprocessor class to inherit abstractprocessor.

In order to use APT Technology to generate code, we first need to design the code template we want to generate. The following code is generated by apt.

package com.example.modular.shop;

public class ARouter$$ShopActivity {
    public static Class<?> findTargetClass(String path) {
        if (path.equals("/shop/ShopActivity")) {
            return ShopActivity.class;
        }
        return null;
    }
}

Implement the process method in the arouterprocessor class, process the supported annotations, and generate the code we want.

/**
 *
 *Equivalent to main function, start processing annotation
 *The core method of annotation processor, dealing with specific annotations and generating java files
 *
 *The @ param set supports the set of annotation types, such as @ arouter annotation
 *The current or previous running environment of @ param roundenvironment. You can find the annotation through this object
 *@ return true means that the subsequent processor will not process it (it has finished processing)
 */
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    //Set set is the set of supported annotations, such as arouter annotation
    if (set.isEmpty()) return false;

    //Get all elements annotated by @ arouter annotation
    Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class);

    if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) {

        for (Element element : elementsAnnotatedWith) {

            //Obtain the package node through the class node (full path name, such as: com. Example. Modular. Shop)
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();

            //Get the simple class name annotated by @ arouter
            String simpleName = element.getSimpleName().toString();

            //Note: Package Name: com.example.modular.shop annotated class name: shopactivity
            Messager. Printmessage (diagnostic. Kind. Note, "package name:" + packagename + "annotated class name:" + simplename);

            //Final generated class file name
            String finalClassName = "ARouter$$" + simpleName;
            try {
                //Create a new source file and return a javafileobject object object to allow writing to it
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName);

                //Get the writer of this file object and turn on the write function
                Writer writer = sourceFile.openWriter();

                writer.write("package " + packageName + ";\n");
                writer.write("public class " + finalClassName + " {\n");
                writer.write("public static Class<?> findTargetClass(String path) {\n");

                //Get arouter annotation
                ARouter aRouter = element.getAnnotation(ARouter.class);

                writer.write("if (path.equals(\"" + aRouter.path() + "\")) {\n");

                writer.write("return " + simpleName + ".class;\n");

                writer.write("}\n");

                writer.write("return null;\n");

                writer.write("}\n}");

                //Close write stream
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return true;
}

When you use it, you need to add a dependency in the build.gradle file, such as in the shopping shop module

dependencies {
    implementation project(path: ':ARouter:annotations')
    annotationProcessor project(path: ':ARouter:compiler')
}

Then add the arouter annotation on the shopactivity class

@ARouter(path = "/shop/ShopActivity")
public class ShopActivity extends AppCompatActivity {}

Finally, build – > make project will generate the code we want.
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Note: in case of Chinese code disorder, add the following configuration in build.gradle:

//Java console output Chinese garbled code
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

APT + javapoet

Java pot is an open source java code generation framework launched by square company, which provides Java API to generate. Java source files. This framework is very practical, and it is also the java object-oriented OOP syntax we are used to. It can be easily used to generate the code according to the annotation. Through this way of automatic code generation, we can replace the tedious repetitive work with a more concise and elegant way.

Project homepage and source code
https://github.com/square/jav…

Depend on Java applet Library

dependencies {
    //Help us generate java code through class calls
    implementation 'com.squareup:javapoet:1.11.1'
}

Eight common classes of JavaApplet

First, let’s take a simple example provided by the official website of javapot to generate the HelloWorld class

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

Use javapot to generate the above code:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

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

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

Eight common classes are provided in the Java applet jar
Componentized architecture design (1): introduction and use of gradle componentized configuration, apt + javapot

Rules for string formatting of JavaApplet

Literal amount of $l, such as: "int value = $l", 10
$s string, such as: $s, "hello"
$t class and interface, such as: $t, mainactivity
$n variable, such as user. $n, name

Next generationARouter$$ShopActivityclass

package com.example.modular.shop;

public class ARouter$$ShopActivity {
    public static Class<?> findTargetClass(String path) {
        return path.equals("/shop/ShopActivity") ? ShopActivity.class : null;
    }
}

Using javapot to generateARouter$$ShopActivityclass

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    //Get all elements annotated by @ arouter annotation
    Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class);

    if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) {

        for (Element element : elementsAnnotatedWith) {

            //Obtain the package node through the class node (full path name, such as: com. Example. Modular. Shop)
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();

            //Get the simple class name annotated by @ arouter
            String simpleName = element.getSimpleName().toString();

            //Note: Package Name: com.example.modular.shop annotated class name: shopactivity
            Messager. Printmessage (diagnostic. Kind. Note, "package name:" + packagename + "annotated class name:" + simplename);

            //Final generated class file name
            String finalClassName = "ARouter$$" + simpleName;

            ARouter aRouter = element.getAnnotation(ARouter.class);

            ClassName targetClassName = ClassName.get((TypeElement) element);
            //Build methodology
            MethodSpec findTargetClass = MethodSpec.methodBuilder("findTargetClass")
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    . returns (class. Class) // return value class <? >
                    .addParameter(String.class, "path")
                    .addStatement("return path.equals($S) ? $T.class : null", aRouter.path(), targetClassName)
                    .build();

            // construction class
            TypeSpec finalClass = TypeSpec.classBuilder(finalClassName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addMethod(findTargetClass)
                    .build();

            //Generate file
            JavaFile javaFile = JavaFile.builder(packageName, finalClass)
                    .build();

            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return true;
}

If my article is helpful to you, please give me a compliment

Recommended Today

Summary: NPM common commands and operations

The full name of NPM is (node package manager), which is a package management and distribution tool installed with nodejs. It is very convenient for JavaScript developersDownload, install, upload and manage installed packages。 First of all, the following variables will be used: < name > | < PKG > module name < version > version […]