How TheRouter Simplifies Android Modular Routing with APT and ASM
This article explains why a routing framework is essential for Android apps, analyzes TheRouter's source code, and shows how it uses annotation processing and ASM bytecode injection to handle page navigation, parameter injection, and cross‑module method calls in a modular architecture.
Why Use a Routing Framework
Routing is indispensable in modern Android development, especially for enterprise apps, because it decouples the strong dependency on Intent for page navigation and reduces cross‑team coupling.
Modular Development Pain Points
Unreasonable module dependencies lead to high coupling, difficult maintenance, and long compile times.
Cross‑module navigation and access are cumbersome.
Why Choose TheRouter
TheRouter offers many proven advantages; see the official comparison table.
TheRouter is maintained by a dedicated team, used in Huolala products, and receives timely fixes and continuous optimization.
TheRouter Source Code Analysis
We examine how TheRouter solves modular development pain points by focusing on core features such as page navigation, route map generation, and parameter injection.
Page Navigation Implementation
Without a routing framework, navigation between modules requires explicit Intent construction or reflection, which is error‑prone and hard to maintain.
Intent intent = new Intent(MainActivity.this, TestActivity.class);
startActivity(intent);A more flexible approach stores mappings in a collection:
HashMap<String, String> activityMap = new HashMap<>();
activityMap.put("xx://xxx.xxx.xxx", "com.therouter.demo.shell.TestActivity");
Intent intent3 = new Intent();
intent3.setComponent(new ComponentName(MainActivity.this, activityMap.get("xx://xxx.xxx.xxx")));
startActivity(intent3);TheRouter simplifies this to two lines:
Add @Route(path = BusinessAPathIndex.INJECT_TEST4) to the target activity.
Call
TheRouter.build(BusinessAPathIndex.INJECT_TEST4).navigation();The navigation method looks up the URL in ROUTER_MAP, retrieves the RouteItem, creates an Intent with the target class, and starts the activity.
@JvmOverloads
fun navigation(ctx: Context?, fragment: Fragment?, requestCode: Int, ncb: NavigationCallback? = null) {
// ...
val match = matchRouteMap(matchUrl)
if (match != null) {
// invoke interceptors, build intent, start activity
} else {
// callback onLost
}
}Route Map Generation via APT
TheRouter uses Annotation Processing (APT) to scan @Route annotations at compile time, creates RouteItem objects, and generates a class that populates ROUTER_MAP with these items.
class TheRouterAnnotationProcessor : AbstractProcessor() {
override fun process(set: Set<TypeElement>?, roundEnv: RoundEnvironment): Boolean {
val routeList = parseRoute(roundEnv)
genRouterMapFile(routeList)
return true
}
}The generated class contains a static addRoute() method that registers each route:
public static void addRoute() {
RouteItem item1 = new RouteItem("http://kymjs.com/business_a/testinject", "com.therouter.demo.shell.TestInjectActivity", "", "");
RouteMapKt.addRouteItem(item1);
// ...
}Injecting the Route Map with ASM
During the build, TheRouter's Gradle plugin applies the therouter plugin, which runs TheRouterTransform. This transform scans all class files, finds the generated RouterMap__TheRouter__* classes, and inserts calls to addRoute() into the empty initDefaultRouteMap() method of TheRouterServiceProvideInjecter using ASM bytecode injection.
public final class TheRouterServiceProvideInjecter {
public static final void initDefaultRouteMap() {
try { RouterMap__TheRouter__2107448941.addRoute(); } catch (Exception e) { e.printStackTrace(); }
try { RouterMap__TheRouter__546452950.addRoute(); } catch (Exception e2) { e2.printStackTrace(); }
}
}Parameter Passing (Autowired)
TheRouter provides @Autowired to inject fields automatically. The APT processor collects @Autowired fields, creates AutowiredItem objects, and generates a class with an autowiredInject method that uses parsers to set field values from the intent or fragment arguments.
public static void autowiredInject(MainActivity target) {
for (AutowiredParser parser : TheRouter.getParserList()) {
Integer intValue = parser.parse("int", target, new AutowiredItem("int", "intValue", 0, "", "com.therouter.demo.shell.MainActivity", "intValue", false, ""));
if (intValue != null) target.intValue = intValue;
// ... other fields
}
}The injection is triggered by TheRouter.inject(this), which internally calls the generated autowiredInject method.
Cross‑Module Method Calls via ServiceProvider
To call methods across modules without direct dependencies, developers define an interface in a base module, implement it in a feature module, and annotate a static provider method with @ServiceProvider. APT generates a ServiceProvider__TheRouter__* interceptor that returns the implementation when TheRouter.get(IUserService.class) is called.
@ServiceProvider
public static IUserService test() {
return new IUserService() {
@Override
public String getUserInfo() { return "这是用户信息"; }
};
}The generated interceptor implements com.therouter.inject.Interceptor and performs a type check to return the correct service instance.
public <T> T interception(Class<T> clazz, Object... params) {
if (IUserService.class.equals(clazz) && params.length == 0) {
IUserService impl = Test.test();
return (T) impl;
}
return null;
}During initialization, ASM injects calls to privateAddInterceptor(new ServiceProvider__TheRouter__...()) into the empty trojan() method of TheRouterServiceProvideInjecter, registering the interceptor in the router's interceptor list.
public static final void trojan() {
try { TheRouter.getRouterInject().privateAddInterceptor(new ServiceProvider__TheRouter__526662154()); } catch (Exception e) { e.printStackTrace(); }
// ... other providers
}Summary
TheRouter combines annotation processing (APT/KSP) and ASM bytecode injection to generate route maps, handle navigation, inject parameters with @Autowired, and provide cross‑module service access via @ServiceProvider. This architecture decouples modules, simplifies page jumps, and reduces boilerplate in large Android projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
