ARouter 原理深度剖析:从 APT 到 ASM,彻底搞懂路由框架
一句话总结:ARouter = APT 编译期生成路由表 + DexFile/assets/ASM 三种方式加载 + Intent 标准跳转
前言
ARouter 是阿里巴巴开源的路由框架,核心解决 Android 组件化/模块化下的页面跳转、服务调用、拦截器等问题。本文从源码层面,把 ARouter 的整个工作原理彻底讲透。
一、整体架构
ARouter 的工作流程分为三个阶段:
编译期(APT) → 构建期(Gradle Plugin + ASM) → 运行期(navigation)
| 阶段 | 干什么 |
|---|---|
| 编译期 | APT 扫描 @Route、@Autowired 注解,生成路由表 Java 文件 |
| 构建期 | Gradle Plugin 用 ASM 修改字节码,消除反射(可选) |
| 运行期 | 查 Warehouse 路由表 → 拦截器链 → Intent 跳转 |
二、编译期:APT 生成路由表
ARouter 利用 APT(Annotation Processing Tool)在编译时扫描所有加了 @Route 注解的类,自动生成三类 Java 文件。
2.1 生成路由表 ------ IRouteGroup
java
@Route(path = "/user/detail")
public class UserDetailActivity extends Activity { ... }
APT 会生成:
java
package com.alibaba.android.arouter.routes;
public class ARouter$$Group$$user implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/user/detail",
RouteMeta.build(
RouteType.ACTIVITY,
UserDetailActivity.class,
"/user/detail",
"user" // 分组:路径第一段
)
);
atlas.put("/user/profile",
RouteMeta.build(
RouteType.ACTIVITY,
UserProfileActivity.class,
"/user/profile",
"user"
)
);
}
}
分组规则 :路径第一段就是组名,/user/detail → user 组,/pay/checkout → pay 组。
2.2 生成根路由表 ------ IRouteRoot(存分组索引)
java
package com.alibaba.android.arouter.routes;
public class ARouter$$Root$$moduleapp implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("user", ARouter$$Group$$user.class);
routes.put("pay", ARouter$$Group$$pay.class);
routes.put("main", ARouter$$Group$$main.class);
}
}
这个就是分组索引表------只存"组名 → 组加载类"的映射。
2.3 生成参数注入 ------ Autowired
java
public class ARouter$$Autowired$$UserDetailActivity implements ISyringe {
@Override
public void inject(Object target) {
UserDetailActivity activity = (UserDetailActivity) target;
activity.id = activity.getIntent().getStringExtra("id");
activity.name = activity.getIntent().getStringExtra("name");
activity.age = activity.getIntent().getIntExtra("age", 0);
}
}
@Autowired 注解在编译期就生成了对应的参数注入代码,避免运行时用反射解析 Intent Extra。
2.4 生成目录结构
ruby
app/build/generated/source/kapt/debug/
└── com/alibaba/android/arouter/routes/
├── ARouter$$Root$$app.java ← 根路由表(分组索引)
├── ARouter$$Root$$module_user.java
├── ARouter$$Group$$main.java ← 路由表
├── ARouter$$Group$$user.java
├── ARouter$$Group$$pay.java
├── ARouter$$Interceptors$$app.java ← 拦截器注册
├── ARouter$$Providers$$app.java ← 服务提供者
├── ARouter$$Autowired$$UserDetailActivity.java ← 参数注入
├── ARouter$$Autowired$$LoginActivity.java
└── ARouter$$Autowired$$PayActivity.java
每个 module 各自生成 ,打包时合并在同一个包 com.alibaba.android.arouter.routes 下。
三、初始化加载阶段(核心演进之路)
这是 ARouter 最有意思的部分------加载方案经历了三次演进。
3.1 方案一:DexFile 全量扫描(最原始的版本)
java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) {
String apkPath = context.getPackageManager()
.getApplicationInfo(context.getPackageName(), 0).sourceDir;
DexFile dexFile = new DexFile(apkPath);
Enumeration<String> entries = dexFile.entries();
while (entries.hasMoreElements()) {
String className = entries.nextElement();
// 只过滤 routes 包下的类
if (className.startsWith("com.alibaba.android.arouter.routes")) {
// 反射实例化,调用 loadInto() 填充路由表
Object instance = Class.forName(className).newInstance();
if (instance instanceof IRouteRoot) {
((IRouteRoot) instance).loadInto(Warehouse.groupsIndex);
}
}
}
}
原理 :运行时通过 DexFile 遍历 APK 中所有类名 ,做 startsWith 匹配,找到 routes 包下的生成类,反射创建实例后调用 loadInto()。
问题:
| 问题 | 影响 |
|---|---|
| 遍历全量类 | 项目大了 APK 有几万个类,每次都要遍历 |
| 首次初始化慢 | MultiDex + 大项目可能 200ms+ |
| 低端机更明显 | 字符串匹配 + 反射,低端机上更卡 |
3.2 方案二:APT 阶段写入文本文件(中间过渡方案)
在 APT 阶段,把所有生成的类全限定名写到一个 text 文件中,打包到 APK 的 assets/arouter/ 目录下:
ruby
assets/arouter/class_cache.txt
com.alibaba.android.arouter.routes.ARouter$$Root$$app
com.alibaba.android.arouter.routes.ARouter$$Root$$module_user
com.alibaba.android.arouter.routes.ARouter$$Group$$main
com.alibaba.android.arouter.routes.ARouter$$Group$$user
com.alibaba.android.arouter.routes.ARouter$$Interceptors$$app
com.alibaba.android.arouter.routes.ARouter$$Providers$$app
...
初始化时直接读文件,不需要遍历全量类:
java
InputStream is = context.getAssets().open("arouter/class_cache.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String className;
while ((className = reader.readLine()) != null) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE)) {
Object instance = Class.forName(className).newInstance();
// ...
}
}
优势:O(1) 定位到需要的类,初始化速度大幅提升。
注意:这不是 SPI(ServiceLoader),就是纯 APT 生成 + 自己读写文本文件。
3.3 方案三:Gradle Plugin + ASM 修改字节码(终极方案)
方案二虽然快了很多,但反射创建对象 的开销仍然存在。终极方案是:在字节码层面 直接改写 LogisticsCenter.init() 方法,把 loadInto() 调用硬编码进去。
ASM 原理:
kotlin
1. APT 生成 IRouteGroup 等 Java 文件
2. javac 编译成 .class 文件
3. Gradle Plugin 拦截 class → dex 的过程
→ 用 ASM 读取 LogisticsCenter.class
→ 在 init() 方法里直接插入调用代码
→ 输出修改后的 .class 文件
ASM 插入前的 init():
java
public void init() {
// 空的,或者只有基础逻辑
}
ASM 插入后,相当于字节码上改成了:
java
public void init() {
new ARouter$$Root$$app().loadInto(Warehouse.groupsIndex);
new ARouter$$Root$$module_user().loadInto(Warehouse.groupsIndex);
new ARouter$$Group$$user().loadInto(Warehouse.routes);
new ARouter$$Group$$pay().loadInto(Warehouse.routes);
new ARouter$$Interceptors$$app().loadInto(Warehouse.interceptors);
new ARouter$$Providers$$app().loadInto(Warehouse.providers);
// 原始 init 逻辑
}
注意 :这里是直接 new + 方法调用,没有反射 ,没有 Class.forName()。
3.4 三种方案对比
| DexFile | APT + 写文件 | ASM Plugin | |
|---|---|---|---|
| 加载方式 | 运行时遍历所有类 | 读 assets 清单 | 编译期直接改字节码 |
| 反射 | ✅ Class.forName + newInstance | ✅ Class.forName + newInstance | ❌ 直接 new |
| 初始化耗时 | 最慢(几十~几百ms) | 中等 | 最快(接近 0) |
| 侵入性 | 无 | 无 | 需要 gradle plugin |
| 代码维护 | 无 | 无 | 代码被魔改,难 debug |
3.5 三合一兜底策略
最新版本 ARouter 是 三合一兜底:
csharp
init() 时:
1. 尝试读 assets/arouter/class_cache.txt → 走反射(方案二)
2. 如果 ASM Plugin 被应用 → LogisticsCenter.class 已被魔改 → 直接 new(方案三)
3. 上述都不行 → 回退到 DexFile 扫描(方案一)
四、运行时导航流程
4.1 调用入口
java
ARouter.getInstance()
.build("/user/detail")
.withString("id", "123")
.navigation(this);
4.2 build() → 构造 Postcard(路由卡片)
java
protected Postcard build(String path) {
// 校验 path 格式
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new HandlerException("...");
}
// 提取分组:/user/detail → group = "user"
String group = path.substring(1, path.indexOf("/", 1));
// 构造空壳 Postcard
return new Postcard(path, group);
}
Postcard 结构:
java
public final class Postcard {
private String path; // "/user/detail"
private String group; // "user"
private String action; // Intent Action
private Uri uri; // 外部 URI
private Bundle extras; // 参数集
private int flags; // Intent flags
private int enterAnim, exitAnim; // 转场动画
private Object tag; // 自定义附加数据
private boolean greenChannel; // 是否跳过拦截器
}
4.3 withString() → 塞参数
就是往 Postcard 的 Bundle 里放数据,每次返回 this,链式调用。
4.4 navigation() → 核心跳转逻辑
这是最核心的部分。 整个跳转就是跟 Warehouse 里的几个 Map 打交道。
4.4.1 Warehouse(全局数据仓库)
java
final class Warehouse {
// ① 路由主表:path → RouteMeta(含目标 class)
static Map<String, RouteMeta> routes = new ConcurrentHashMap<>();
// ② 分组索引:group → IRouteGroup.class(用完即删)
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new ConcurrentHashMap<>();
// ③ 拦截器索引
static Map<Class<?>, Integer> interceptorsIndex = new IdentityHashMap<>();
// ④ 拦截器实例(已排序)
static List<IInterceptor> interceptors = new ArrayList<>();
// ⑤ Provider 缓存(已创建的服务实例)
static Map<Class, IProvider> providers = new ConcurrentHashMap<>();
// ⑥ Provider 索引
static Map<String, RouteMeta> providersIndex = new ConcurrentHashMap<>();
}
Warehouse = 全局唯一的内存数据中心,所有东西最终都存在这里。
4.4.2 导航核心流程
vbnet
navigation("/user/detail")
│
▼
① routes.get("/user/detail") ← 先查路由主表
│
├── 命中 → 直接拿到 Class<?> → 跳转到 ③
│
└── 未命中 →
│
▼
② groupsIndex.get("user") ← 查分组索引
│
├── 未命中 → 抛异常 "No route match"
│
└── 命中 → ARouter$$Group$$user.class
│
▼
反射 new ARouter$$Group$$user()
.loadInto(Warehouse.routes) ← 加载整个 user 组
Warehouse.groupsIndex.remove("user") ← 用完即删
│
▼
再查 routes.get("/user/detail") → 必然命中
│
▼
③ 拿到 RouteMeta.destination = UserDetailActivity.class
│
▼
构建 Intent → startActivity
为啥要分组+懒加载?
ruby
Warehouse 初始状态:
groupsIndex = {
"user" → ARouter$$Group$$user.class,
"main" → ARouter$$Group$$main.class,
"pay" → ARouter$$Group$$pay.class
}
routes = {} ← 空的!
第一次跳 /user/detail:
→ routes.get("/user/detail") = null
→ groupsIndex.get("user") = ARouter$$Group$$user.class
→ 加载 user 组所有路由到 routes
→ routes.get("/user/detail") → 命中!
→ groupsIndex.remove("user")
第二次跳 /user/profile:
→ routes.get("/user/profile") → 命中!(上一步已加载)
→ 不需要再查 groupsIndex
按需分组加载------不是一次性加载所有路由,第一次跳到某个组才加载该组的路由表。
4.5 拦截器链
java
// _ARouter.java
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(mContext, postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
callback.onLost(postcard);
}
});
拦截器实现:
java
@Interceptor(priority = 7)
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if (需要登录) {
callback.onInterrupt(new RuntimeException("需要登录"));
} else {
callback.onContinue(postcard);
}
}
}
原理 :按 priority 排序后的拦截器列表,在子线程中链式执行,每个拦截器决定放行或拦截。
4.6 _navigation() → 真正跳转
java
private void _navigation(Context context, Postcard postcard, int requestCode, ...) {
switch (postcard.getType()) {
case ACTIVITY:
Intent intent = new Intent(context, postcard.getDestination());
intent.putExtras(postcard.getExtras());
if (requestCode > 0) {
((Activity) context).startActivityForResult(intent, requestCode);
} else {
context.startActivity(intent);
}
break;
case PROVIDER:
// 服务提供者 → 直接 new 实例
Object instance = postcard.getDestination().newInstance();
if (instance instanceof IProvider) {
((IProvider) instance).init(context);
}
break;
case FRAGMENT:
Fragment fragment = (Fragment) postcard.getDestination().newInstance();
fragment.setArguments(postcard.getExtras());
break;
}
}
最终就是一发 Intent ,所有复杂的 APT、ASM、懒加载、拦截器链,最后落到底就是 startActivity(intent)。
五、完整时序图
scss
你的代码 ARouter LogisticsCenter Warehouse Interceptors Android Framework
│ │ │ │ │ │
├─ build("/user/detail") │ │ │ │
│ └─ 构造 Postcard ──→ │ │ │ │ │
│ │ │ │ │ │
├─ withString() ────→ │ │ │ │ │
│ │ │ │ │ │
├─ navigation() │ │ │ │ │
│ ├─ completion() ────→ │ │ │ │
│ │ ├─ routes.get(path)─→│ │ │
│ │ │ ← null(首次加载) │ │ │
│ │ ├─ groupsIndex.get()→│ │ │
│ │ │ ← Group.class │ │ │
│ │ ├─ loadInto() ──────→│ routes.put() │ │
│ │ ├─ routes.get(path)─→│ │ │
│ │ │ ← RouteMeta │ │ │
│ │ │ │ │ │
│ ├─ 填 Postcard ←───── │ │ │ │
│ │ │ │ │ │
│ ├─ doInterceptions() │ ├─ interceptors ────→│ │
│ │ │ │ ├─ process() ──┐ │
│ │ │ │ │ 校验逻辑 │ │
│ │ │ │ │ ← onContinue() │ │
│ │ │ │ │ │
│ ├─ _navigation() │ │ │ │
│ │ └─ Intent + startActivity ──────────────────────────────────────→│ ActivityManager │
│ │ │ │ │ │
│ ← 跳转完成 │ │ │ │
六、ARouter 还做了哪些有意思的事
6.1 Service 暴露(模块间解耦)
不仅仅是 Activity 跳转,ARouter 还能暴露接口服务:
java
// 定义接口
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 实现
@Route(path = "/service/hello")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) { return "Hello " + name; }
}
// 调用
HelloService service = (HelloService) ARouter.getInstance()
.build("/service/hello").navigation();
原理和 Activity 路由一样,只不过 navigation() 返回的是实例。
6.2 SharedPreferences 缓存
ARouter 还用 SharedPreferences 做了快速短路:
java
// 判断 APK 版本/签名指纹是否变化
// 没变 → 路由表还在内存里,跳过所有扫描
// 变了 → 重新扫描,更新 SP
6.3 参数注入(@Autowired)
java
@Route(path = "/user/detail")
public class UserDetailActivity extends Activity {
@Autowired
String id; // 自动从 Intent Extra 注入
@Autowired(name = "nick")
String name; // 别名映射
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this); // 触发注入
}
}
java
package com.alibaba.android.arouter.routes;
public class ARouter$$Autowired$$UserDetailActivity
implements ISyringe {
@Override
public void inject(Object target) {
UserDetailActivity activity = (UserDetailActivity) target;
activity.id = activity.getIntent()
.getStringExtra("id");
activity.name = activity.getIntent()
.getStringExtra("name");
activity.age = activity.getIntent()
.getIntExtra("age", 0);
}
}
java
// _ARouter.java
public void inject(Object target) {
// 1. 通过 target 的类名,找到编译期生成的 AutowiredService 实现类
// 类名规则:target 类全名 + "$$ARouter$$Autowired"
String className = target.getClass().getName();
// 比如 target = UserDetailActivity
// className = "com.example.UserDetailActivity$$ARouter$$Autowired"
// 2. 从缓存取,没取到就反射创建
ISyringe autowiredHelper = Warehouse.autowiredCache.get(className);
if (null == autowiredHelper) {
// 反射加载生成的类
autowiredHelper = (ISyringe) Class.forName(className).newInstance();
Warehouse.autowiredCache.put(className, autowiredHelper);
}
// 3. 调用注入逻辑
autowiredHelper.inject(target);
}
其实就是根据类名找ARouter AutowiredUserDetailActivity,然后调用inject 编译期生成对应注入代码,运行时直接赋值,无反射开销。
总结
理解 ARouter,记住三句话:
- 编译期用 APT 生成路由表、根路由表、Autowired 注入代码
- 加载方式经历 DexFile 全量扫 → assets 文件读 → ASM 改字节码 三次演进,核心目标是消除反射
- 运行时就是查 Warehouse 的 Map → 拦截器链 → 发 Intent,没什么黑魔法
好了,现在你对 ARouter 的理解可以超过 90% 的 Android 开发者了。