1, some basic use of annotations; 2, gradle 5.4.1 version of how to correctly imported com. Google. Auto. Services: auto - service: 1.0 - rc7 class library; 3. Use Javapoet to write Java files; How to generate code at compile time Use reflection to execute Java class files generated at compile time.Copy the code

The cause of

In the project, I saw that the router routing framework written by the middle station could jump by using the value after the annotation, so I became interested and explored the basic principle of the implementation inside.

Specific performance of the following code:

// Annotate an activity @route (path =)"/module_mall/my_points_activity") public class MyPointsActivity // jump arouter.getInstance ().build()"/module_mall/my_points_activity")
.navigation();
Copy the code

The main implementation process is as follows:

The implementation process

Custom annotations

@Target(elementType.type) means that the annotation is applied only to classes, and @Retention(retentionPolicy.class) means that the lifetime of the annotation is compile-time.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Router {
   String value();
}
Copy the code

Custom subclasses of AbstractProcessor

Next, create another Java Library module to subclass AbstractProcessor. The gradle file for this module is defined as follows:

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    annotationProcessor 'com. Google. Auto. Services: auto - service: 1.0 rc7'// We need to introduce an annotation from auto-service. Otherwise compileOnly will not be generated correctly'com. Google. Auto. Services: auto - service: 1.0 rc7' 
    implementation 'com. Squareup: javapoet: 1.13.0'Implementation Project ();':my_annotation'// This is the custom module implementation Project (path:':apt') // This is a custom module}sourceCompatibility = "Seven"
targetCompatibility = "Seven"
Copy the code

Attention: the annotationProcessor to introduce com. Google. Auto. Services: auto – service: 1.0 – rc7, so as to ensure the correct code generation, corresponding to the second question here.

GetSupportedAnnotationTypes method returns a collection of the set that is used to store the compiler support scanning annotation types.

@Override
public Set<String> getSupportedAnnotationTypes() {
    LinkedHashSet<String> types = new LinkedHashSet<>();
    types.add(Router.class.getCanonicalName());
    return types;
}
Copy the code

The process method is called during compilation, at which point we can use Javapoet to generate the corresponding Java code. The specific explanation is already clear in the code and can be viewed against it.

@Override public boolean process(Set<? Extends TypeElement> annotations, RoundEnvironment roundEnv) {} extends Element> annotatedElement = roundEnv.getElementsAnnotatedWith(Router.class); /** * public void handle(Map<String, Class<? >> routeMap) {*} * * Build parameters of type Map, map <String, Class<? >> Type */ ParameterizedTypeNametypeName = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ParameterizedTypeName.get( ClassName.get(Class.class), WildcardTypeName.subtypeOf(Object.class) ) ); MethodSpec = MethodSpec. MethodBuilder ();"handle"// addAnnotation(overrides. Class) // addAnnotation(overrides. Class) // addAnnotation(overrides. // return void. AddParameter (ParameterSpec. Builder ())typeName, "routeMap").build()); // The method parameter name is routeMapfor(Element Element: annotatedElement) {// 1, ElementKind.CLASS; // 2, ElementKindif (element.getKind() == ElementKind.CLASS) {
            TypeElement typeElement = (TypeElement) element; / / get annotation class package name information PackageElement PackageElement = (PackageElement) element. GetEnclosingElement (); / / by class name for annotation class object of the classname classname classname = classname. Get (packageElement. GetQualifiedName (). The toString (),typeElement.getSimpleName().toString()); // Get the Router annotation = element.getannotation (router.class); // 1, annotation.value(); // 2, annotation.$N.put($S.$T.class) there are three placeholders$N,$S,$T/ / 3.$NIt can represent methods, variables, and so on;$SRepresents a string;$TMethodspec.addstatement (MethodSpec.addStatement)"$N.put($S.$T.class)"."routeMap", annotation.value(), className); } } ClassName interfaceName = ClassName.get(RouteTable.class); / / the dynamically generated class, the class name is written death, actually can use the package name to generate specific Java class TypeSpec helloWorld = TypeSpec. ClassBuilder ("PerryRouteTable"// PUBLIC class. AddSuperinterface (interfaceName) // Implement interface. AddMethod (methodSpec.build()) // Implement methods.build(); JavaFile = Javafile.builder (aptManager.package_name, helloWorld).build(); Try {// from javapoet dynamic generation method javafile.writeto (processIngenv.getFiler ()); } catch (IOException e) { e.printStackTrace(); }return true;
}
Copy the code

At this point, build our code, will be in the following path

Automatically generate the following code:

public class PerryRouteTable implements RouteTable { @Override public void handle(Map<String, Class<? >> routeMap) { routeMap.put("third_activity",ThirdActivity.class); }}Copy the code

Invoke the code in build using reflection

Write relationships between strings and classes to the map collection.

public class AptManager {
    public static final String PACKAGE_NAME = "com.perry.router"; private static Map<String, Class<? >> sRouteTable = new HashMap<>(); public static void registerModule(String moduleName) { String routeTableName = PACKAGE_NAME +"."+ moduleName; try { Class<? > clazz = Class.forName(routeTableName); Constructor constructor = clazz.getConstructor(); RouteTable instance = (RouteTable) constructor.newInstance(); instance.handle(sRouteTable); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Class<? > getClazzFromKey(String routerKey) {if (sRouteTable.isEmpty()) return null;
        returnsRouteTable.get(routerKey); }}Copy the code

We use reflection to call the handle method in our main module. For simplicity, I only generate one Java file by default. The logic is that each module will generate a different Java file, then loop through and call the handle method using reflection.

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        AptManager.registerModule("PerryRouteTable"); }}Copy the code

The above is the whole implementation process, if you have questions, welcome to discuss.