At the beginning, I discovered ActivityRouter when I was learning the routing framework. However, this project only had code implementation and usage methods, and lacked the introduction of implementation principles and implementation process. For beginners who just learned the Router, they didn’t know how to start and learn it. This article documented how to use the annotation processor, culminating in the simplest version of an Activity routing framework.
To reduce learning costs, this demo uses the Java language. The code has been uploaded to Github
Step 1 – Create the Annotation Module
Create the Annotation Java Library module that defines the RouterAnnotation annotation, value representing the URI address of the page.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface RouterAnnotation {
String value();
}
Copy the code
Step 2: Create the Processor Module
Create a Processors Java Library module that inherits AbstractProcessor to create an annotation processor.
@AutoService(Processor.class) public class RouterProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { System.out.println("---- process ----") return false; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>(); annotations.add(RouterAnnotation.class.getCanonicalName()); return annotations; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); }}Copy the code
Configure the annotation handler so that it can participate at compile time. Here we use the framework provided by Google. Add the following dependencies to build.gradle in the Processor module
Implementation 'com. Google. Auto. Services: auto - service: 1.0 -rc3' / / the annotation processor is annotationProcessor for autoService annotations to take effect "Com. Google. Auto. Services: auto - service: 1.0 -rc3" implementation project (' ': an annotation)Copy the code
Step 3: Configure the app Moudle
Next we add our defined annotation handler dependencies under the project APP module.
implementation project(':annotation')
annotationProcessor project(':processor')
Copy the code
Configure the RouterAnnotation for MainActivity
@RouterAnnotation("demo://main_activity")
public class MainActivity extends AppCompatActivity
Copy the code
Gradlew build builds the project and the output “—- process —-” indicates that the annotation processor ran successfully.
Step 4: Dynamically generate routing mapping files
Having successfully involved the annotation handler in the compilation process with the example above, let’s give The RouterProcess a bigger role by generating the Activity page routing table and starting the Activity with a URI address.
First, get the @RouterAnnotation annotation in the project, get the address of the page URI, and the class name that defines the annotation Activity. Javapoet is then used to dynamically generate Java files that insert the URI address of each page, along with the class name of the corresponding Activity, into the routing table. When the URI page is opened through the Router, the routing table can be matched and the page can then be started using startActivity
Next we look at the code implementation process 👇
@Override public boolean process(Set<? Extends TypeElement> set, RoundEnvironment RoundEnvironment) {generateRouterInit(); // First get the annotation element Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(RouterAnnotation.class); // Define a map method of public static type MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); // Iterate over the annotation Element for (Element Element: elements) { if (element.getKind() == ElementKind.CLASS) { RouterAnnotation router = element.getAnnotation(RouterAnnotation.class); // Get the activity's class name ClassName ClassName ClassName = classname. get((TypeElement) element); String path = router.value(); // Generate code for phones. map(URI, xxx.class); / / here is the uri and activity records inserted into the Routers in the routing table. MapMethod addStatement (" com. Xiwna. Processor. The router. Routers. The map ($S, $tc lass, null)", path, className); } } mapMethod.addCode("\n"); TypeSpec helloWorldClass = TypeSpec. ClassBuilder ("RouterMapping").addModiFIERS (Modifier.PUBLIC, Modifier.FINAL) .addMethod(mapMethod.build()) .build(); JavaFile javaFile = JavaFile.builder("com.xiwna.processor.router", helloWorldClass) .build(); try { javaFile.writeTo(filer); } catch (IOException e) { // e.printStackTrace(); } } return falseCopy the code
Routermapping.java is generated in app/build/generated/ap_generated_sources
So, we generate a RouterMapping file. Call the phones. map method to insert a routing record. The methods are as follows. We will introduce the implementation of the Routers routing table later.
Public static void map(String path, Class<? extends Activity> activity, MethodInvoker method) { mappings.add(new Mapping(path, activity, method)); }Copy the code
The map method constructs the URI and activity class as Mapping objects and places them in the mappings collection.
Let’s see how the routerMapping.map method in this file is executed to insert the route record into the routing table. So that’s the generateRouterInit method that we talked about earlier.
Private void generateRouterInit() {// Generate public static init method methodSpec.Builder initMethod = MethodSpec.methodBuilder("init") .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC); // This method is implemented as routerMapping.map () initmethod.addStatement ("RouterMapping.map()"); TypeSpec RouterInit = TypeSpec. ClassBuilder ("RouterInit").addModiFIERS (Modifier.PUBLIC, Modifier.FINAL) .addMethod(initMethod.build()) .build(); try { JavaFile.builder("com.xiwna.processor.router", routerInit) .build() .writeTo(filer); } catch (Exception e) { // e.printStackTrace(); }}Copy the code
See here, we are regenerating a file to initialize the routing table. At this point, the annotation processor is done.
Step 5: Open the page URI using the Router
Next we implement the Routers routing table.
Public class Mapping {private final String path; private final Class<? extends Activity> activity; private final MethodInvoker method; private String formatHost; public Mapping(String path, Class<? extends Activity> activity, MethodInvoker method) { this.path = path; this.activity = activity; this.method = method; formatHost = Uri.parse(path).getHost(); Private static List<Mapping> mappings = new ArrayList<>(); Private static void initIfNeed() {if (mappings. IsEmpty ()) {routerInit.init (); }} public static void map(String path, Class<? extends Activity> activity, MethodInvoker method) { mappings.add(new Mapping(path, activity, method)); } /** * Open the activity on the router ** @param url * @return */ public static Boolean open(context context, String url) { initIfNeed(); Uri uri = Uri.parse(url); // Traverse the routing table to match the URI. If the uri is matched successfully, the Activity page on the other side is started. mappings) { if (mapping.match(uri)) { Intent intent = new Intent(context, mapping.getActivity()); intent.putExtras(mapping.parseExtras(uri)); context.startActivity(intent); return true; } } return false; }}Copy the code
The mapping table provides a map method that stores the RouterMapping route mapping records generated by the previous annotation handler and stores them in the Mappings list. Then, when the URI is opened, the routing table is initialized and the appropriate pages are matched.
The matching rule is simple, mainly to check whether the host addresses are the same
public boolean match(Uri uri) {
return this.formatHost.equals(uri.getHost());
}
Copy the code
After the match is passed, Activty will be started. If there are parameters in URI link, it will be resolved.
public Bundle parseExtras(Uri uri) {
Bundle bundle = new Bundle();
Set<String> names = UriCompact.getQueryParameterName(uri);
for (String name: names) {
String value = uri.getQueryParameter(name);
put(bundle, name, value);
}
return bundle;
}
Copy the code
Launch the target Activity page
{
intent intent = new Intent(context, mapping.getActivity());
intent.putExtras(mapping.parseExtras(uri));
context.startActivity(intent);
}
Copy the code
Step 6: Add a stub module
At this point, the gradlew build will cause a compilation error. That is because the RouterInit.init() method in the initIfNeed method of the Routers is generated during compilation. So we need to have the RouterInit and RouterMapping files at compile time.
Here we add stub modules and rely on them only during compileOnly Project (‘:stub’). This way, you can let the compilation pass and use the file generated by the annotation processor when the package runs.
At this point, a complete and simple version of the Router framework is implemented. In a componentized project, each individual module only needs to insert a routing record into the routing table, so that the Router, as the base module, can open the page of each module. We hope you can implement ActivityRouter step by step through this demo.