Componentized Architecture Design: gradle Componentized Configuration, Introduction and Use of APT+javapoet

Blog Home Page Reference resources: Gradle Core (5): Gradle Advanced Customization gradle componentized configuration The configuration of Android com...
gradle componentized configuration
Componentized Detailed Deployment
Interaction between Module and Module
Introduction and Use of APT
APT + javapoet

Blog Home Page

Reference resources:

gradle componentized configuration

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

A brief introduction to gradle

Gradle syntax: Explain the journey of component design starting with the log output of gradle

// The first way to print strings println("hello, gradle!") // The second way to print strings println "hello2, gradle!"

From these two types of string output, it can be seen that the method does not write parentheses or semicolons after a sentence, which is a groovy feature.

You can use the println function to output logs in the build.gradle file in your Android project.Then view the log of build output from Build->Toggle view

Custom Properties

Gradle can add additional custom attributes, implemented through the ext attribute.Start by creating a new config.gradle file and customizing the isRelease property for dynamic switching: Componentized/Integrated

ext { // false: Componentized mode (sub-modules can run independently) // true: integrated mode (packages the entire project apk, sub-modules cannot run independently) isRelease = true }

So how do I use this config file?The config.gradle file needs to be referenced in the project's root build.gradle file by apply front

// build.gradle // Can be referenced by apply from apply from: 'config.gradle'

Then use custom attributes in the build.gradle file of the app application project

// build.gradle // With custom attributes, attributes need to be written in ${} println "$"
Basic configuration of config.gradle file

Create a new shop Library module with the following project structure:

Looking at the build.gradle file in the app Application module and the shop library module, we find that the configuration of the red box above is similar. Can we configure it in a modular way, using 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 the related attribute information, and configure it as follows:

// config.gradle ext { // Define Map access version-related information, key name can be arbitrary versions = [ compileSdkVersion: 29, buildToolsVersion: "29.0.2", minSdkVersion : 21, targetSdkVersion : 29, versionCode : 1, versionName : "1.0" ] }

Then access the build.gradle file in the app Application module and the shop library module to access the properties defined in the Map collection, such as the custom properties in the Map in the app Application module build.gradle

// Get Map def versions = rootProject.ext.versions android { // Access values directly from 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
Switching from a component module to an integrated module requires setting the applicationId in order for the component module to run independently

So you also need to configure different applicationId properties

// config.gradle ext { // Set different applicationId s when switching between componentization and integration 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. Switching between production and formal environment configuration in code
Sometimes you need to switch between production and formal environment configurations in your code, such as the URL of a network request.Android provides us with custom BuildConfig functionality.

// config.gradle ext { baseUrl = [ debug : "https://127.0.0.1/debug ", //beta URL release: "https://127.0.0.1/relase "//Official Version URL" ] }

Configuration via buildConfigField in the app Application module build.gradle file is accessible in 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", "\"$\"") } release { buildConfigField("String", "baseUrl", "\"$\"") } } }

3. dependencies Dependency Configuration

// config.gradle ext { appcompatVersion = "1.0.2" constraintlayoutVersion = "1.1.3" dependencies = [ appcompat : "androidx.appcompat:appcompat:$", constraintlayout: "androidx.constraintlayout:constraintlayout:$", ] 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 notation // implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.0.2' // implementation supports.appcompat // implementation supports.constraintlayout // Support dependency supports.each { key, value -> implementation value } testImplementation tests.junit androidTestImplementation tests.espresso androidTestImplementation tests.androidJunit }

4. Signature Configuration
Note when configuring signatures: signingConfigs must be written before buildTypes

android { // Signature Configuration (Invisible pit: must be written before buildTypes) signingConfigs { debug { // Tiankeng: Wrong fill, compilation failed and no problem found storeFile file('/Users/xujinbing839/.android/debug.keystore') storePassword "android" keyAlias "androiddebugkey" keyPassword "android" } release { // Signature Certificate File storeFile file('/Users/xujinbing839/work/mycode/todo/todo_modular/keystore/modular') storeType "modular" // Type of signing certificate storePassword "123456" // Password for signing certificate file keyAlias "modular" // Key Alias in Signing Certificate keyPassword "123456" // The password of the key in the signing certificate v2SigningEnabled true // Whether to turn on 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 Subpackage Configuration // multiDexKeepFile file('multidex-config.txt') // Generate a png picture of a specified dimension from an svg picture // vectorDrawables.generatedDensities('xhdpi','xxhdpi') // Use support-v7 compatibility (version 5.0 and above) vectorDrawables.useSupportLibrary = true // Keep only specified and default resources resConfigs('zh-rCN') // Configure the so library CPU architecture (real machine: arm, simulator: x86) // x86 x86_64 mips mips64 ndk { //abiFilters('armeabi', 'armeabi-v7a') // For simulator startup abiFilters('x86', 'x86_64') } } // AdbOptions can add configurations to adb operation options adbOptions { // Configure operation timeout in milliseconds timeOutInMs = 5 * 1000_0 // Option Configuration for the adb install command installOptions '-r', '-s' } // Configuration for dx operations, accepts a closure of type DexOptions, provided by DexOptions dexOptions { // Configure the maximum heap memory allocated for dx command execution javaMaxHeapSize "4g" // Configuring whether to pre-execute the dex Libraries project will increase the incremental build speed after opening, but will affect the clean build speed, default true preDexLibraries = false // Configure whether to turn on jumbo mode by forcing it on for more than 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 per package // '--main-dex-list='+'/multidex-config.txt', //packaged into the file list of the main classes.dex '--minimal-main-dex' ] } // Run the lint check by executing the gradle lint command, and the default generated report is in outputs/lint-results.html lintOptions { // Build terminates when a link check error occurs, typically set to false abortOnError false // Treat warnings as errors (older version: warningAsErros) warningsAsErrors false // Check for new API s check 'NewApi' } }

Componentized Detailed Deployment

Significance of Component Development

What is component development?
Component development is the process of dividing an app into modules, each of which is a component. During the development process, we can make these components depend on each other or debug some components individually, but ultimately when it is published, these components are merged into one apk, which is component development.

Componentized and plug-in development differs slightly:
During plug-in development, the entire app is divided into many modules, including a host and many plug-ins, each of which is an apk (each component module is a lib), and the host APK and the plug-in APK are packaged separately when they are finally packaged.

Why componentize?
1. Development needs
Non-interdependent, interactive, arbitrary combination, highly decoupled
2. Team effectiveness

Differentiation, switching between library and application

Phone Module is different from Android Library:

Phone Module is a stand-alone, compiled into an apk, and the build.gradle file needs to be configured with applicationId; Android Library cannot run independently, it cannot be compiled into a single apk, and the build.gradle file does not need to be configured with applicationId.

Phone Module and Android Library switch:

Here is an example of a sub-module shopping shop:

If the Shopping shop module can compile the apk independently, it needs to switch to Phone Module (i.e. component mode) and switch dynamically through isRelease:

  1. When isRelease is true: integrated mode (that is, the entire project apk can be packaged), sub-module shopping shops cannot run independently
  2. When isRelease is false: Componentized mode, sub-module shop can run independently

Where the isRelease variable is a property defined in config.gradle

// build.gradle if (isRelease) { // If it is a released 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, there cannot be an applicationId if (!isRelease) applicationId appId.shop } }

When a sub-module shopping shop switches from a library module to an application module, you may need to write test code, such as a startup entry.

Using the sourceSets configuration, put the test code in the debug folder, and when switching to integrated mode, remove all debug code when packaging into an apk (that is, debug code is not packaged into an apk).

android { // Configure resource paths, facilitate test environments, package not integrated into formal environments sourceSets { main { if (!isRelease) { // If it is a componentized mode, it needs to be run separately manifest.srcFile 'src/main/debug/AndroidManifest.xml' } else { // Integrated mode, whole project packaging apk manifest.srcFile 'src/main/AndroidManifest.xml' java { // Files in the debug directory do not need to be merged into the main project when releasing exclude '**/debug/**' } } } } }

Componentized Development Specification:

  1. Submodule Shopping Shop: Add a prefix to the src class and res resource commands, shop_, such as shop_activity_home
  2. app module: can not be modified, default

Interaction between Module and Module

How do modules interact with each other (including jumps, parameterizations, etc)?There are many ways:

  1. EventBus is very messy and difficult to maintain
  2. Reflection reflection technology can be successful, but it is expensive to maintain and has a high version @hide limitation
  3. Implicit intent maintenance costs are good, it's cumbersome, and Manifest's action needs to be maintained
  4. BroadCastReceiver needs to be registered dynamically (after 7.0) and the requester sends the broadcast
  5. Class loading requires accurate full class name paths, which are costly to maintain and prone to human error

First implementation: class loading technology interaction

Loading a target class through the forName of the Class requires an accurate knowledge of 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(); } }

Second implementation: Global Map records path information

To jump, if you know the Class object of the target class, you can not jump. Next, you just need to solve the Class object lookup of the target class.

You can define a PathBean to encapsulate information about the target class, such as the full path name of the target class, the Class object of the target class

public class PathBean { public String path; // Jump Target Full Class Name public Class<?> targetClass; // Jump Class Object of Target Class public PathBean(String path, Class<?> targetClass) { this.path = path; this.targetClass = targetClass; } }

There are many PathBean s in a module that can be accessed from a List, and many modules that can be used to differentiate between different modules 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, and key is the module name, such as: shop module; value is all Activity path information under that module.

All path information is added to the mGroupMap and the target class object is obtained from the group name (module name) and path name when used.

/** * 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 the target class based on the group name and path name */ 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; }

Introduction and Use of APT

Introduction to APT

APT (Annotation Processing Tools) is a tool for handling annotations. It detects source code files to find Annotaions in them and uses Annotation for additional processing.Annotation processors can generate additional source files and other files based on Annotaion in the source files (the specific contents of the files are determined by the author of the Annotaion processor). APT also compiles the generated source files and the original source files to generate the class files together.

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

1. APT Core Implementation Principles

The basic principle of compile-time Annotation parsing is that annotations are added to certain code elements, such as types, functions, fields, and so on. At compile-time, the javac compiler examines the subclasses of AbstractProcessor, calls the process function of that type, and then passes all the annotated elements into the process function so that the developer can go where the compiler is concernedFor example, generating new java classes from annotations is the basic principle of open source libraries such as ARouter, Butterknife, Dragger, and so on.

2. Javaa Source File Programming Layer Class File

The tool is a javac tool, and the Annotation Processor is an annotation tool in javac that is used for compile-time scanning and processing.You can think of it as a specific annotation and register your own annotation processor.

3. How to register Annotation Processors
MyProcessor to javac.You must provide a.Jar file.Like other.Jar files, you pack your annotation processor into this file.Also, in your jar, you need to package a specific file, javax.annotation.processing.Processor, to the META-INF/services path.

Knowledge Point Details

1. jar

  • com.google.auto.service:auto-service Google provided java generated source code library
  • com.squareup:javapoet provides various API s to generate java code files in various postures

2. @AutoService

This is an annotation introduced in other annotation processors.The AutoService Annotation Processor was developed by Google to generate META-INF/services/javax.annotation.processing.Processor files.We can use annotations in annotation processors.Very convenient

3. Structural Language

It is also a structured language for java source files.An interface for describing structural language elements is provided in the JDK.

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: 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 ) {} }

Element Program Elements

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 a comment type element. 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 working with TypeMirror

Filer
With Filer you can create java files

AbstractProcessor Core API

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

Equivalent to the main function () of each processor.Here you can write your code to scan, evaluate, and process annotations, as well as generate java files.Enter the parameter RoundEnvironment, which allows you to query for annotated elements that contain specific annotations.

2. getSupportedAnnotationTypes()

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

3. getSupportedSourceVersion()

Used to specify the version of java you are using.Usually SourceVersion.latestSupported() is returned here.You can also return to SourceVersion.RELEASE_6 if you have good reasons to support java 6 only

4. getSupportedOptions()

Option parameters used to specify annotation processor processing.Option parameter values need to be configured in the gradle file

// Configure option parameter values in the gradle file (for APT parameter reception) // Remember: must be written under defaultConfig node javaCompileOptions { annotationProcessorOptions { arguments = [moduleName: project.getName()] } }

These API s can also be specified in annotations:

// AutoService is a fixed way of writing, just add a comment // AutoService Annotation Processor can be automatically generated from @AutoService in auto-service for registration // Used to generate META-INF/services/javax.annotation.processing.Processor file @AutoService(Processor.class) // Allow/support annotation types for annotation processor processing (new annotation module) @SupportedAnnotationTypes({"com.example.modular.annotations.ARouter"}) // Specify JDK Compiled Version @SupportedSourceVersion(SourceVersion.RELEASE_7) // Annotate parameters received by the processor @SupportedOptions("moduleName") public class ARouterProcessor extends AbstractProcessor { // ignore }

Processing Environment Core API
// ignore public class ARouterProcessor extends AbstractProcessor { // Operating Element Tool Class (classes, functions, attributes are Elements) private Elements elementUtils; // Type (class information) tool class, containing tool methods for manipulating TypeMirror private Types typeUtils; // Messager is used to report errors, warnings, and other prompts private Messager messager; // File Generator Class/Resource, Filter used to create new source, class, and auxiliary files private Filer filer; // Module name, passed in from getOptions to get build.gradle private String moduleName; // This method is mainly used for some initialization operations, and the Processing Environment parameter of this method gives you a few useful tool classes @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); // ProceingEnv is a parent protected property that can be used directly. // This is actually the parameter ProcessingEnvironment of the init method // processingEnv.getMessager(); //reference source 64 lines elementUtils = processingEnvironment.getElementUtils(); messager = processingEnvironment.getMessager(); filer = processingEnvironment.getFiler(); typeUtils = processingEnvironment.getTypeUtils(); // Get the parameters passed in by build.gradle through Processing Environment Map<String, String> options = processingEnvironment.getOptions(); if (options != null && !options.isEmpty()) { moduleName = options.get("moduleName"); // Pit: Diagnostic.Kind.ERROR, exception ends automatically, not as good as Log.e in Android messager.printMessage(Diagnostic.Kind.NOTE, "moduleName=" + moduleName); } } }

RoundEnvironment Core API
// ignore public class ARouterProcessor extends AbstractProcessor { /** * Equivalent to main function, start processing annotations * Annotate the core approach of the processor, handle specific annotations, and generate Java files * * @param set A set of nodes supporting processing annotations is used (annotations are written above the class) * @param roundEnvironment The current or previous running environment through which you can find annotations. * @return true Indicates that subsequent processors will no longer process (process completed) */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set.isEmpty()) return false; // Get all class nodes with ARouter annotations Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class); // Traverse all class nodes for (Element element : elements) { // ignore } // ignore return true; } }

Element Core API

APT usage

Compatibility of development environment

https://github.com/google/auto

1. Android Studio 3.2.1 + Gradle 4.10.1 Critical Version

dependencies { // Register notes and generate configuration information for META-INF, rc2 has pits after gradle5.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 is downward compatible

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' }
Use APT technology to help us generate code

1. ARouter annotations

Create a new java library project named annotations.Then create the ARouter annotation

/** * <ul> * <li>@Target(ElementType.TYPE) // Interfaces, Classes, Enumerations, Comments </li> * <li>@Target(ElementType.FIELD) // Constants of attributes, enumerations </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)// The note is used on another note </li> * <li>@Target(ElementType.PACKAGE) // Package </li> * <li>@Retention(RetentionPolicy.RUNTIME) <br>Annotations will exist in the class byte code file and can be retrieved by reflection when the jvm is loaded </li> * </ul> * * Life cycle: SOURCE < CLASS < RUNTIME * 1,In general, if you need to get annotation information dynamically at runtime, annotate with RUNTIME * 2,To do some preprocessing at compile time, such as ButterKnife, annotate with CLASS.Annotations will exist in the class file but will be discarded at runtime * 3,Do some checking, such as @Override, annotate with the SOURCE source code.Comment only exists at source level and is discarded at compile time */ @Target(ElementType.TYPE) // This annotation works on the class @Retention(RetentionPolicy.CLASS) // Some preprocessing is done at compile time.Annotations will exist in the class file public @interface ARouter { // Detailed routing path (required), such as "app/MainActivity" String path(); // Routing group name (optional, intercepted from path if not filled in) String group() default ""; }

2. Custom ARouterProcessor Annotation Processor
Create a new java library project named compiler.Create an ARouterProcessor class that inherits AbstractProcessor.

In order to generate code using APT technology, first you need to design the code template that we want to generate. The code below 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 annotations * Annotate the core approach of the processor, handle specific annotations, and generate Java files * * @param set A collection that supports annotation types, such as: @ARouter annotations * @param roundEnvironment Current or previous runtime environment where you can find annotations * @return true Indicates that subsequent processors will no longer process (process completed) */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { // Set collection is the set of supported annotations, such as ARouter annotations if (set.isEmpty()) return false; // Get all elements commented by the @ARouter comment Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class); if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) { for (Element element : elementsAnnotatedWith) { // Get the package node from the class node (full path name, such as com.example.modular.shop) String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); // Get a simple class name annotated by @ARouter String simpleName = element.getSimpleName().toString(); // Note: Package name: The annotated class name of com.example.modular.shop: 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 to allow writing to it JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + finalClassName); // Get the Writer for this file object, turn on writing 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 annotations 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; }

Add dependencies to the build.gradle file when using, 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, the code we want will be generated.

Note: If Chinese scrambling occurs, add the following configuration to build.gradle:

// java console output Chinese scrambling tasks.withType(JavaCompile) { options.encoding = "UTF-8" }

APT + javapoet

javapoet is an open source java code generation framework introduced by square, which provides java api generation.java source files.This framework is very practical and is also our customary java object-oriented OOP syntax.It can be easily used to generate code for annotations.This way of automating code generation allows us to replace tedious, repetitive work in a simpler and elegant way.

Project Home Page and Source Code
https://github.com/square/jav...

Dependent on javapoet Libraries

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

javapoet 8 Common Classes

Let's start with a simple example from the javapoet website, which generates the HelloWorld class

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

Generate the above code using javapoet:

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 commonly used classes are provided in javapoet jar

javapoet string formatting rules

A literal quantity of $L, such as "int value=$L", 10 The $S string, such as: $S,'hello' $T class, interface, such as: $T, MainActivity The $N variable, such as user.$N, name

Next, generate the ARouter$$ShopActivity class

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

Generate ARouter$$ShopActivity class using javapoet

public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { // Get all elements commented by the @ARouter comment Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(ARouter.class); if (elementsAnnotatedWith != null && !elementsAnnotatedWith.isEmpty()) { for (Element element : elementsAnnotatedWith) { // Get the package node from the class node (full path name, such as com.example.modular.shop) String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); // Get a simple class name annotated by @ARouter String simpleName = element.getSimpleName().toString(); // Note: Package name: The annotated class name of com.example.modular.shop: 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 Method Body 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(); // Build Class TypeSpec finalClass = TypeSpec.classBuilder(finalClassName) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(findTargetClass) .build(); // Generate Files 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, you might as well give me a pat on the back (^^)

3 December 2019, 16:08 | Views: 1417

Add new comment

For adding a comment, please log in
or create account

0 comments