ARouter路由实践设计
# 05.ARouter路由框架设计原理
# 目录介绍
- 01.整体概述介绍
- 1.1 项目背景介绍
- 1.2 基础概念
- 1.3 设计目标
- 1.4 一些问题思考
- 02.ARouter设计思路
- 2.1 整体设计思路
- 2.2 路由表生成设计
- 2.3 路由表加载设计
- 2.4 导航流程设计
- 2.5 拦截器链设计
- 2.6 服务发现设计
- 2.7 参数自动注入设计
- 03.APT编译时处理原理
- 3.1 APT是什么
- 3.2 注解处理器工作原理
- 3.3 RouteProcessor生成代码
- 3.4 InterceptorProcessor生成代码
- 3.5 AutowiredProcessor生成代码
- 04.ARouter初始化原理
- 4.1 init()初始化流程
- 4.2 路由表扫描机制
- 4.3 分组加载策略
- 4.4 Gradle Plugin优化加载
- 05.导航过程源码分析
- 5.1 build()构建Postcard
- 5.2 navigation()执行导航
- 5.3 路由查找过程
- 5.4 拦截器执行过程
- 5.5 最终跳转过程
- 06.核心技术点思考
- 6.1 为什么需要路由框架
- 6.2 路由与原生跳转的对比
- 6.3 分组懒加载的设计
- 6.4 降级策略设计
- 6.5 多模块路由协作
- 07.ARouter与其他路由对比
- 7.1 主流路由框架对比
- 7.2 TheRouter的改进
- 7.3 路由框架选型建议
- 08.优秀代码设计深度解析
- 8.1 设计模式应用全景
- 8.2 Postcard继承体系设计
- 8.3 Warehouse单例仓库核心设计
- 8.4 LogisticsCenter路由补全算法
- 8.5 拦截器CancelableCountDownLatch设计
- 8.6 JavaPoet代码生成设计
- 8.7 两级索引架构思想
- 8.8 _ARouter门面模式设计
- 8.9 Provider单例缓存设计
- 8.10 设计思想总结
- 09.面试高频问题深度解析
- 9.1 经典面试题
- 9.2 进阶面试题
- 9.3 学习建议
# 01.整体概述介绍
# 1.1 项目背景介绍
在组件化/模块化架构中,各模块之间不能直接引用对方的类,传统的Intent显式跳转方式无法跨模块使用。例如:
传统跳转方式的问题:
// 模块A想跳转到模块B的页面
Intent intent = new Intent(context, ModuleBActivity.class);
startActivity(intent);
// 编译错误!模块A无法引用模块B的类
组件化架构中的模块依赖关系:
app
├── module-home(首页模块)
├── module-shop(商城模块)
├── module-user(用户模块)
└── module-base(基础公共模块)
module-home和module-shop之间不互相依赖
→ 无法直接使用对方的Activity类
→ 需要路由框架来解决跨模块跳转问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ARouter是阿里巴巴开源的Android路由框架,它通过URL路径映射Activity/Fragment/Service等组件,解决了组件化架构中的页面跳转和服务调用问题。
# 1.2 基础概念
路由框架核心概念:
路由(Route):
URL路径到目标组件的映射关系
例如:"/user/login" → LoginActivity.class
路由表(Route Table):
所有路由映射关系的集合
在编译时由APT生成,运行时加载
导航(Navigation):
根据URL路径查找目标组件并执行跳转
Postcard(明信片):
封装一次导航的所有信息(路径、参数、动画等)
类似于Intent的角色
拦截器(Interceptor):
在导航过程中可以拦截、修改或取消跳转
如登录拦截器:未登录时跳转到登录页
服务发现(Service Discovery):
跨模块调用服务的机制
通过接口调用而非直接引用实现类
参数注入(Autowired):
自动将Intent中的参数注入到目标页面的字段中
类似于Spring的@Autowired
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 1.3 设计目标
ARouter的核心设计目标:
- 解耦页面跳转:通过URL映射替代显式Intent,模块间无直接依赖
- 支持拦截器:在跳转前执行自定义逻辑(登录验证、权限检查等)
- 服务发现:跨模块调用接口服务,不依赖具体实现
- 参数自动注入:减少Intent参数解析的样板代码
- 降级策略:路由找不到目标时的兜底处理
- 编译时处理:APT编译时生成路由表,避免运行时反射开销
# 1.4 一些问题思考
思考问题:
1. ARouter是如何实现编译时生成路由表的?APT原理是什么?
2. 路由表是何时加载的?如何做到分组懒加载?
3. 导航过程中路由是如何查找目标的?
4. 拦截器是如何设计的?多个拦截器如何按顺序执行?
5. 跨模块服务调用是如何实现的?接口下沉到哪里?
6. ARouter在多模块项目中是如何协作的?
7. ARouter有什么局限性?现在有哪些更好的替代方案?
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 02.ARouter设计思路
# 2.1 整体设计思路
ARouter的整体架构分为三层:
ARouter整体架构:
┌──────────────────────────────────────┐
│ API层(ARouter对外接口) │
│ init() / build() / navigation() │
│ inject() / addInterceptor() │
└────────────────┬─────────────────────┘
│
┌────────────────▼─────────────────────┐
│ 核心层(_ARouter内部实现) │
│ ┌─────────────────────────────────┐ │
│ │ Warehouse(路由仓库) │ │
│ │ ├── groupsIndex (Group索引表) │ │
│ │ ├── routes (路由表) │ │
│ │ ├── providers (服务表) │ │
│ │ └── interceptors (拦截器表) │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ LogisticsCenter(物流中心) │ │
│ │ 负责路由查找、Postcard填充 │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ InterceptorService(拦截器服务) │ │
│ │ 责任链模式执行拦截器 │ │
│ └─────────────────────────────────┘ │
└────────────────┬─────────────────────┘
│
┌────────────────▼─────────────────────┐
│ 编译时(APT生成代码) │
│ RouteProcessor → 生成路由注册代码 │
│ InterceptorProcessor → 拦截器注册 │
│ AutowiredProcessor → 参数注入代码 │
└──────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 2.2 路由表生成设计
ARouter使用APT(Annotation Processing Tool)在编译时扫描@Route注解,为每个模块生成路由注册代码:
路由表生成过程:
编译时:
1. 开发者在Activity上添加@Route注解
@Route(path = "/user/login")
public class LoginActivity extends Activity { ... }
2. APT扫描所有@Route注解
RouteProcessor.process()
→ 扫描所有标记了@Route的类
→ 按group分组(path的第一段,如/user/login的group是"user")
→ 为每个group生成一个路由注册类
3. 生成的代码示例:
// 每个group对应一个类
public class ARouter$$Group$$user implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/user/login", RouteMeta.build(
RouteType.ACTIVITY,
LoginActivity.class,
"/user/login",
"user",
null, // paramsType
-1, // priority
-2147483648 // extra
));
}
}
// 每个模块对应一个Root类(Group索引)
public class ARouter$$Root$$moduleuser implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("user", ARouter$$Group$$user.class);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 2.3 路由表加载设计
路由表加载策略 — 分组懒加载:
初始化时(init):
只加载Group索引(IRouteRoot)
→ 将每个group名称映射到对应的IRouteGroup类
→ 此时不加载具体路由信息
→ 启动速度快
导航时(navigation):
根据path提取group名称
→ 检查该group是否已加载
├── 已加载 → 直接从routes表中查找
└── 未加载 → 实例化IRouteGroup → 调用loadInto()加载该group的所有路由
→ 后续同group的路由直接从内存查找
示例:
init()时加载:
groupsIndex = {
"user" → ARouter$$Group$$user.class,
"shop" → ARouter$$Group$$shop.class,
"home" → ARouter$$Group$$home.class
}
首次导航"/user/login"时:
→ group = "user"
→ 发现routes中没有 → 加载ARouter$$Group$$user
→ routes中加入 "/user/login" → LoginActivity
→ routes中加入 "/user/register" → RegisterActivity
→ 后续/user/*路由直接查找routes
优势:
→ 初始化只加载索引,非常快
→ 路由信息按需加载,减少内存占用
→ 大型项目中路由可能有数百个,全量加载太慢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 2.4 导航流程设计
完整导航流程:
ARouter.getInstance()
.build("/user/login") // 构建Postcard
.withString("name", "yang") // 附带参数
.withInt("age", 25)
.navigation(context) // 执行导航
Step 1: build("/user/login")
→ 创建Postcard对象
→ 提取path和group
→ Postcard继承自RouteMeta,附加了参数Bundle等
Step 2: navigation(context)
→ _ARouter.getInstance().navigation(context, postcard, ...)
Step 3: LogisticsCenter.completion(postcard)
→ 根据path查找RouteMeta
├── 找到 → 将RouteMeta信息填充到Postcard
│ → 设置destination(目标Activity类)
│ → 设置type(ACTIVITY/FRAGMENT/PROVIDER等)
└── 找不到 → 触发降级策略
Step 4: 执行拦截器链
→ 遍历所有注册的拦截器
→ 按priority依次执行
→ 任何拦截器可以中断导航
Step 5: 执行跳转
→ 根据type执行不同逻辑:
├── ACTIVITY → 构建Intent + startActivity
├── FRAGMENT → 实例化Fragment并返回
├── PROVIDER → 获取/创建Provider实例
└── 其他 → 对应处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 2.5 拦截器链设计
拦截器机制设计:
注册拦截器:
@Interceptor(priority = 1, name = "登录拦截器")
public class LoginInterceptor implements IInterceptor {
@Override
public void init(Context context) {
// 拦截器初始化
}
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if (needLogin(postcard) && !isLoggedIn()) {
// 拦截:跳转到登录页
callback.onInterrupt(new RuntimeException("需要登录"));
} else {
// 放行:继续执行下一个拦截器
callback.onContinue(postcard);
}
}
}
拦截器执行流程(责任链模式):
navigation() → InterceptorServiceImpl.doInterceptions()
→ 在子线程中依次执行拦截器:
index = 0
while (index < interceptors.size()) {
interceptor = interceptors.get(index)
interceptor.process(postcard, new InterceptorCallback() {
onContinue: index++ → 执行下一个
onInterrupt: 中断导航,回调onLost
})
}
→ 所有拦截器都放行 → 执行最终跳转
→ 有超时机制(默认300秒),超时自动中断
注意事项:
├── 拦截器在子线程中异步执行
├── 必须调用callback.onContinue()或callback.onInterrupt()
├── 不调用callback会导致导航卡住(直到超时)
├── priority数字越小优先级越高
└── Fragment跳转不经过拦截器(isGreenChannel=true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 2.6 服务发现设计
跨模块服务调用设计:
1. 在公共模块(base)定义接口
public interface IUserService extends IProvider {
UserInfo getUserInfo();
boolean isLoggedIn();
}
2. 在用户模块实现接口
@Route(path = "/user/service")
public class UserServiceImpl implements IUserService {
@Override
public void init(Context context) { }
@Override
public UserInfo getUserInfo() { return userInfo; }
@Override
public boolean isLoggedIn() { return token != null; }
}
3. 在其他模块通过ARouter获取服务
// 方式1:通过路径发现
IUserService userService = (IUserService) ARouter.getInstance()
.build("/user/service")
.navigation();
// 方式2:通过类型发现(推荐)
IUserService userService = ARouter.getInstance()
.navigation(IUserService.class);
boolean loggedIn = userService.isLoggedIn();
服务发现原理:
→ Provider类型的路由在初始化时会被预加载
→ 通过路径查找:从routes表中查找
→ 通过类型查找:从providers表中查找(Key是接口Class)
→ Provider实例默认是单例模式(缓存在Warehouse中)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 2.7 参数自动注入设计
参数自动注入(@Autowired):
使用方式:
@Route(path = "/user/detail")
public class UserDetailActivity extends Activity {
@Autowired
String name;
@Autowired(name = "userId") // 自定义参数名
int id;
@Autowired
IUserService userService; // 也可以注入Provider服务
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this); // 触发注入
}
}
APT生成的注入代码:
public class UserDetailActivity$$ARouter$$Autowired implements ISyringe {
@Override
public void inject(Object target) {
UserDetailActivity substitute = (UserDetailActivity) target;
substitute.name = substitute.getIntent().getStringExtra("name");
substitute.id = substitute.getIntent().getIntExtra("userId", 0);
substitute.userService = (IUserService) ARouter.getInstance()
.navigation(IUserService.class);
}
}
inject()调用流程:
ARouter.getInstance().inject(this)
→ 查找 "类名$$ARouter$$Autowired" 类
→ 实例化并调用 inject(this)
→ 从Intent中提取参数设置到对应字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 03.APT编译时处理原理
# 3.1 APT是什么
APT(Annotation Processing Tool)是Java的注解处理工具,它在编译时扫描和处理注解,可以生成新的Java源文件。
APT工作时机:
.java源文件
↓ javac编译
APT处理(在编译期间)
→ 扫描注解
→ 生成新的.java文件
↓ 继续编译
.class文件
关键:APT生成的代码在编译时完成,不影响运行时性能
与运行时反射扫描相比,性能优势巨大
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3.2 注解处理器工作原理
ARouter的三个注解处理器:
1. RouteProcessor
处理@Route注解
→ 为每个module生成路由注册类
→ ARouter$$Root$$moduleName
→ ARouter$$Group$$groupName
2. InterceptorProcessor
处理@Interceptor注解
→ 生成拦截器注册类
→ ARouter$$Interceptors$$moduleName
3. AutowiredProcessor
处理@Autowired注解
→ 为每个使用@Autowired的类生成参数注入类
→ ClassName$$ARouter$$Autowired
注解处理器继承AbstractProcessor:
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.alibaba.android.arouter.facade.annotation.Route")
public class RouteProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
// 获取所有@Route注解的元素
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
// 按group分组
// 使用JavaPoet生成Java代码
// 写入文件
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 3.3 RouteProcessor生成代码
RouteProcessor的处理逻辑:
1. 收集所有@Route注解的类
→ Activity, Fragment, Provider等
2. 按group分组
@Route(path = "/user/login") → group = "user"
@Route(path = "/user/register") → group = "user"
@Route(path = "/shop/detail") → group = "shop"
3. 为每个group生成一个IRouteGroup实现类
ARouter$$Group$$user:
loadInto(atlas) {
atlas.put("/user/login", RouteMeta.build(ACTIVITY, LoginActivity.class, ...));
atlas.put("/user/register", RouteMeta.build(ACTIVITY, RegisterActivity.class, ...));
}
4. 为每个module生成一个IRouteRoot实现类
ARouter$$Root$$moduleuser:
loadInto(routes) {
routes.put("user", ARouter$$Group$$user.class);
}
5. 使用JavaPoet库生成代码
JavaPoet提供类型安全的代码生成API
→ TypeSpec.classBuilder() 生成类
→ MethodSpec.methodBuilder() 生成方法
→ CodeBlock.builder() 生成代码块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 3.4 InterceptorProcessor生成代码
InterceptorProcessor生成:
输入:
@Interceptor(priority = 1, name = "登录拦截")
class LoginInterceptor implements IInterceptor { ... }
@Interceptor(priority = 2, name = "权限检查")
class PermissionInterceptor implements IInterceptor { ... }
生成:
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(1, LoginInterceptor.class);
interceptors.put(2, PermissionInterceptor.class);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.5 AutowiredProcessor生成代码
AutowiredProcessor生成:
输入:
@Route(path = "/user/detail")
class UserDetailActivity extends Activity {
@Autowired String name;
@Autowired int age;
@Autowired(name = "data") Parcelable userData;
}
生成:
public class UserDetailActivity$$ARouter$$Autowired implements ISyringe {
@Override
public void inject(Object target) {
UserDetailActivity t = (UserDetailActivity) target;
t.name = t.getIntent().getStringExtra("name");
t.age = t.getIntent().getIntExtra("age", 0);
t.userData = t.getIntent().getParcelableExtra("data");
}
}
支持的参数类型:
├── 基本类型:int, long, float, double, boolean, String
├── Serializable对象
├── Parcelable对象
├── IProvider服务(通过ARouter.navigation获取)
└── Object类型(序列化为JSON,需自定义SerializationService)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 04.ARouter初始化原理
# 4.1 init()初始化流程
ARouter.init(application) 初始化流程:
ARouter.init(app)
→ _ARouter.init(app)
→ LogisticsCenter.init(context, executor)
↓
Step 1: 获取APT生成类的包名
packageName = "com.alibaba.android.arouter.routes"
Step 2: 扫描该包名下所有类
方式1: 扫描dex文件(默认方式)
→ 遍历所有dex文件
→ 找到以"com.alibaba.android.arouter.routes"开头的类
→ 耗时操作!
方式2: 使用arouter-register Gradle Plugin(推荐)
→ 编译时通过Transform API扫描
→ 直接将注册代码插入到init()方法中
→ 运行时无需扫描,初始化速度大幅提升
Step 3: 分类加载
→ 以"ARouter$$Root"开头的 → 加载到Warehouse.groupsIndex
→ 以"ARouter$$Interceptors"开头的 → 加载到Warehouse.interceptorsIndex
→ 以"ARouter$$Providers"开头的 → 加载到Warehouse.providersIndex
Step 4: 初始化拦截器
→ 遍历interceptorsIndex
→ 实例化所有拦截器
→ 调用interceptor.init(context)
→ 存入Warehouse.interceptors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 4.2 路由表扫描机制
默认扫描机制(性能较差):
ClassUtils.getFileNameByPackageName()
→ 获取应用的所有dex文件路径
→ 遍历每个dex文件
→ 使用DexFile.entries()获取所有类名
→ 过滤出以"com.alibaba.android.arouter.routes"开头的类
→ 返回类名集合
性能问题:
→ 大型应用可能有数万个类
→ 遍历所有类名非常耗时(首次可能需要1-3秒)
→ 特别是multidex场景下,有多个dex文件需要扫描
→ 严重影响启动速度
优化策略:
→ ARouter将扫描结果缓存到SharedPreferences
→ 下次启动直接读取缓存(版本号不变时)
→ 但首次安装或版本更新时仍需扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.3 分组加载策略
分组懒加载详细设计:
Warehouse中的数据结构:
groupsIndex: Map<String, Class<? extends IRouteGroup>>
→ 初始化时加载,存储group名称到Group类的映射
→ 如:{"user" → ARouter$$Group$$user.class}
routes: Map<String, RouteMeta>
→ 按需加载,存储path到RouteMeta的映射
→ 如:{"/user/login" → RouteMeta(ACTIVITY, LoginActivity.class)}
加载时机:
init()时 → 只加载groupsIndex
navigation("/user/login")时
→ group = "user"
→ 检查routes中是否有"/user/login"
├── 有 → 直接使用
└── 没有 → 从groupsIndex取出ARouter$$Group$$user.class
→ 实例化 → 调用loadInto(routes)
→ 该group的所有路由加入routes
→ 从groupsIndex移除该group(已加载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4.4 Gradle Plugin优化加载
arouter-register Gradle Plugin 优化原理:
传统方式(运行时扫描dex):
init() → 扫描dex → 找到ARouter$$Root$$xxx → 实例化 → 注册
└── 首次启动耗时1-3秒
Plugin方式(编译时注入):
编译阶段:
Gradle Plugin通过Transform API
→ 扫描所有class文件
→ 找到ARouter$$Root$$xxx等类
→ 将注册代码直接插入到LogisticsCenter.loadRouterMap()方法中
生成的代码:
void loadRouterMap() {
registerByPlugin = true;
register("com.alibaba.android.arouter.routes.ARouter$$Root$$moduleuser");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$moduleshop");
register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$app");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$app");
}
运行时:
init() → 直接调用loadRouterMap() → 注册代码已在其中 → 无需扫描dex
└── 初始化耗时从秒级降到毫秒级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 05.导航过程源码分析
# 5.1 build()构建Postcard
ARouter.getInstance().build("/user/login")
build(path):
→ 提取group(path的第一段)
→ 创建Postcard对象
→ Postcard继承RouteMeta
→ 额外包含:Bundle, flags, enterAnim, exitAnim等
Postcard支持链式配置:
.withString("key", "value") // 字符串参数
.withInt("age", 25) // 整数参数
.withParcelable("data", obj) // Parcelable参数
.withFlags(Intent.FLAG_ACTIVITY_NEW_TASK) // Intent flags
.withTransition(R.anim.enter, R.anim.exit) // 转场动画
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // 添加flags
.withObject("complex", obj) // 复杂对象(需SerializationService)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 5.2 navigation()执行导航
navigation()核心流程:
_ARouter.navigation(context, postcard, requestCode, callback)
↓
Step 1: 预处理回调
NavigationCallback callback(可选)
→ onFound():找到路由
→ onLost():路由不存在
→ onArrival():跳转完成
→ onInterrupt():被拦截器中断
Step 2: LogisticsCenter.completion(postcard)
→ 查找路由信息,填充Postcard
Step 3: 拦截器处理
if (!postcard.isGreenChannel()) {
// 非绿色通道,需要经过拦截器
interceptorService.doInterceptions(postcard, callback)
}
Step 4: 执行跳转
_navigation(context, postcard, requestCode, callback)
→ 根据postcard.getType()分发:
├── ACTIVITY → buildIntent → startActivity
├── PROVIDER → 返回Provider实例
├── FRAGMENT → 实例化Fragment并设置参数
└── 其他类型 → 对应处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 5.3 路由查找过程
LogisticsCenter.completion(postcard) 详细过程:
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (routeMeta == null) {
// 路由表中没有 → 尝试加载对应group
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
if (groupMeta == null) {
// 没有找到group → 路由不存在
throw new NoRouteFoundException("没有找到路由: " + postcard.getPath());
}
// 实例化group类,加载该group的所有路由
IRouteGroup group = groupMeta.getConstructor().newInstance();
group.loadInto(Warehouse.routes);
// 从groupsIndex中移除(已加载,避免重复加载)
Warehouse.groupsIndex.remove(postcard.getGroup());
// 重新查找
completion(postcard); // 递归调用
return;
}
// 找到路由 → 填充Postcard
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
// 如果是Provider类型,设置为绿色通道(不经过拦截器)
if (routeMeta.getType() == RouteType.PROVIDER) {
postcard.greenChannel();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 5.4 拦截器执行过程
拦截器责任链执行:
InterceptorServiceImpl.doInterceptions(postcard, callback):
// 在子线程中执行拦截器
executorService.execute(() -> {
CancelableCountDownLatch counter = new CancelableCountDownLatch(1);
_execute(0, counter, postcard); // 从第0个拦截器开始
// 等待拦截器链执行完毕(有超时机制)
counter.await(postcard.getTimeout(), TimeUnit.SECONDS);
if (counter.getCount() > 0) {
// 超时 → 中断
callback.onInterrupt("导航超时");
}
});
_execute(int index, CountDownLatch counter, Postcard postcard):
if (index < interceptors.size()) {
IInterceptor interceptor = interceptors.get(index);
interceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// 放行 → 执行下一个拦截器
_execute(index + 1, counter, postcard);
}
@Override
public void onInterrupt(Throwable exception) {
// 中断 → 停止执行
counter.cancel();
callback.onInterrupt(exception);
}
});
} else {
// 所有拦截器都放行 → 执行跳转
counter.countDown();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 5.5 最终跳转过程
_navigation()最终跳转:
switch (postcard.getType()) {
case ACTIVITY:
// 构建Intent
Intent intent = new Intent(context, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// 设置flags
int flags = postcard.getFlags();
if (flags != 0) intent.setFlags(flags);
// 在主线程执行跳转
if (requestCode >= 0) {
// startActivityForResult
activity.startActivityForResult(intent, requestCode);
} else {
context.startActivity(intent);
}
// 转场动画
if (postcard.getEnterAnim() >= 0 && postcard.getExitAnim() >= 0) {
activity.overridePendingTransition(
postcard.getEnterAnim(), postcard.getExitAnim());
}
break;
case PROVIDER:
// 返回Provider实例(从缓存中获取或新建)
return postcard.getProvider();
case FRAGMENT:
// 实例化Fragment
Fragment fragment = postcard.getDestination().newInstance();
fragment.setArguments(postcard.getExtras());
return fragment;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 06.核心技术点思考
# 6.1 为什么需要路由框架
疑惑:Android原生的Intent跳转有什么不足?
问题1:组件化后无法显式跳转
模块间不互相依赖 → 无法引用对方的Activity类
→ Intent(context, OtherActivity.class) 编译报错
问题2:隐式Intent存在问题
→ 需要在Manifest中声明intent-filter
→ 管理混乱,容易冲突
→ 不支持参数类型检查
→ 不支持拦截器
问题3:缺乏统一的跳转管理
→ 无法统一处理降级策略
→ 无法统一添加登录拦截
→ 无法统一记录跳转埋点
路由框架解决了这些问题:
→ URL映射替代类引用 → 解耦
→ 拦截器机制 → 统一拦截处理
→ 降级策略 → 路由失败兜底
→ 服务发现 → 跨模块服务调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 6.2 路由与原生跳转的对比
路由 vs 原生Intent对比:
| 特性 | 路由框架 | 原生Intent |
|---------------|----------------------|---------------------|
| 跨模块跳转 | 支持(URL映射) | 不支持(显式Intent) |
| 拦截器 | 支持 | 不支持 |
| 降级策略 | 支持 | 不支持 |
| 参数自动注入 | 支持(@Autowired) | 不支持 |
| 服务发现 | 支持 | 不支持 |
| 编译时检查 | 部分支持 | 支持 |
| 性能开销 | 略高(查表+反射) | 低 |
| 学习成本 | 中等 | 低 |
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 6.3 分组懒加载的设计
为什么要分组懒加载?
问题:大型App可能有数百个路由
如果init时全量加载所有路由 → 启动变慢
如果每次导航都扫描所有路由 → 导航变慢
解决:两级索引 + 懒加载
第一级:groupsIndex(Group索引)→ init时加载,量很小
第二级:routes(具体路由)→ 按需加载,只在首次使用该group时
类比:字典索引
→ groupsIndex 相当于目录(按拼音分组)
→ routes 相当于正文页面
→ 查字时先翻目录定位到对应页,再在页面中查找
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 6.4 降级策略设计
路由找不到目标时的降级策略:
方式1:全局降级(DegradeService)
@Route(path = "/service/degrade")
class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
// 全局降级处理
// 如:跳转到404页面、弹Toast等
Toast.makeText(context, "页面不存在", Toast.LENGTH_SHORT).show();
}
}
方式2:单次降级(NavigationCallback)
ARouter.getInstance()
.build("/xxx/notexist")
.navigation(context, new NavigationCallback() {
@Override
public void onLost(Postcard postcard) {
// 该次导航的降级处理
}
});
优先级:单次降级 > 全局降级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 6.5 多模块路由协作
多模块路由协作机制:
编译时:
每个模块独立运行APT → 生成各自的路由注册类
module-user → ARouter$$Root$$moduleuser + ARouter$$Group$$user
module-shop → ARouter$$Root$$moduleshop + ARouter$$Group$$shop
module-home → ARouter$$Root$$modulehome + ARouter$$Group$$home
运行时:
ARouter.init() → 扫描所有模块的注册类 → 统一加载到Warehouse
→ 所有模块的路由在同一个Warehouse中
→ 任何模块都可以导航到任何其他模块的页面
注意事项:
1. path必须全局唯一,不同模块不能使用相同path
2. group名称建议与模块名对应,避免混淆
3. 公共接口(IProvider)定义在base模块中
4. 每个模块的ARouter注解处理器独立工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 07.ARouter与其他路由对比
# 7.1 主流路由框架对比
Android主流路由框架对比:
| 特性 | ARouter | TheRouter | WMRouter |
|---------------|---------------|---------------|---------------|
| 开发者 | 阿里巴巴 | 货拉拉 | 美团 |
| 路由表生成 | APT | APT + KSP | APT |
| 初始化方式 | dex扫描/Plugin| Transform | dex扫描 |
| 分组加载 | 支持 | 支持 | 支持 |
| 拦截器 | 支持 | 支持 | 支持 |
| 服务发现 | 支持 | 支持 | 支持 |
| 参数注入 | 支持 | 支持 | 不支持 |
| Kotlin支持 | 一般 | 优秀 | 一般 |
| KSP支持 | 不支持 | 支持 | 不支持 |
| 动态路由 | 不支持 | 支持 | 支持 |
| 维护状态 | 维护较少 | 活跃 | 一般 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 7.2 TheRouter的改进
TheRouter相比ARouter的改进:
1. 编译速度优化
ARouter: 使用APT(kapt对Kotlin项目编译速度影响大)
TheRouter: 同时支持APT和KSP
KSP编译速度比kapt快约2倍
2. 初始化优化
ARouter: 默认扫描dex,需要Gradle Plugin优化
TheRouter: 编译时直接生成初始化代码,无需扫描
3. 动态路由支持
ARouter: 不支持运行时添加路由
TheRouter: 支持动态添加和修改路由表
4. 更好的Kotlin支持
ARouter: 主要为Java设计
TheRouter: 原生Kotlin设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 7.3 路由框架选型建议
选型建议:
小型项目(<5个模块):
→ 可以不用路由框架
→ 隐式Intent + 接口下沉足够
中型项目(5-20个模块):
→ ARouter(成熟稳定,社区资源丰富)
→ TheRouter(如果使用Kotlin + KSP)
大型项目(>20个模块):
→ TheRouter(更好的编译速度和扩展性)
→ 或自研路由框架(满足特定需求)
注意:
路由框架会增加学习成本和维护复杂度
不要为了用路由而用路由
先明确是否真正需要组件化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 08.优秀代码设计深度解析
# 8.1 设计模式应用全景
ARouter作为一个成熟的路由框架,在架构设计中运用了多种经典设计模式:
ARouter中的设计模式全景:
1. 门面模式(Facade)
ARouter类是整个框架的门面,对外暴露简洁的API
→ ARouter.init() / build() / navigation() / inject()
→ 内部所有复杂逻辑委托给_ARouter处理
→ 用户只需要与ARouter交互,无需了解内部实现
public final class ARouter {
public static void init(Application application) {
// 实际工作委托给_ARouter
if (!hasInit) {
_ARouter.init(application);
hasInit = true;
}
}
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
}
设计价值:
→ 隐藏了Warehouse、LogisticsCenter等内部类
→ 降低了使用门槛
→ ARouter可以随时替换内部实现而不影响使用者
2. 责任链模式(Interceptor Chain)
拦截器按priority排序形成链:
LoginInterceptor(1) → PermissionInterceptor(2) → TrackInterceptor(3)
每个拦截器处理后决定:
→ onContinue() → 传递给下一个拦截器
→ onInterrupt() → 中断整条链
与OkHttp拦截器链的区别:
→ OkHttp:同步责任链,链式调用chain.proceed()
→ ARouter:异步责任链,通过callback回调
→ ARouter拦截器在子线程执行,适合异步操作(如网络检查登录态)
3. 工厂模式(APT代码生成)
APT生成的IRouteGroup就是路由元数据的工厂:
ARouter$$Group$$user.loadInto(atlas)
→ 批量生产RouteMeta对象并注册到路由表
→ 每个module独立生成自己的工厂类
→ 运行时按需调用工厂方法加载路由
4. 策略模式(RouteType分发)
不同的RouteType使用不同的导航策略:
ACTIVITY → 构建Intent + startActivity
FRAGMENT → 反射实例化 + 设置Arguments
PROVIDER → 获取/创建单例 + 调用init
_navigation()中的switch-case就是策略选择器
5. 单例模式(多层次单例)
ARouter.getInstance() → 双重检查锁单例
_ARouter.getInstance() → 双重检查锁单例
Provider实例 → Warehouse缓存实现的单例
InterceptorServiceImpl → 通过路由发现的单例服务
→ 多层次单例保证了框架的全局一致性
6. 建造者模式(Postcard)
Postcard支持链式参数配置:
ARouter.getInstance()
.build("/user/login")
.withString("name", "yang")
.withInt("age", 25)
.withFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.withTransition(R.anim.enter, R.anim.exit)
.navigation();
→ 将复杂的导航参数构建过程链式化
→ 每个with方法返回自身(Postcard)
7. 模板方法模式(AbstractProcessor)
APT处理器继承AbstractProcessor,重写process()方法:
→ init():初始化处理环境
→ getSupportedAnnotationTypes():声明处理哪些注解
→ process():核心处理逻辑(子类实现)
→ Java编译器驱动整个处理流程(模板方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 8.2 Postcard继承体系设计
Postcard的继承设计是ARouter中一个优秀的面向对象设计案例:
RouteMeta(路由元数据)
├── type: RouteType → 路由类型
├── destination: Class<?> → 目标类
├── path: String → 路由路径
├── group: String → 路由分组
├── priority: int → 优先级
├── extra: int → 额外标记
└── paramsType: Map → 参数类型映射
↑ extends
Postcard(导航明信片)
├── uri: Uri → URI
├── tag: Object → 标记
├── mBundle: Bundle → 携带参数
├── flags: int → Intent flags
├── timeout: int → 超时时间
├── provider: IProvider → Provider实例
├── greenChannel: boolean → 是否跳过拦截器
├── enterAnim/exitAnim → 转场动画
└── optionsCompat: Bundle → ActivityOptions
设计精髓分析:
1. 为什么Postcard继承RouteMeta而不是包含RouteMeta?
→ 继承使得Postcard天然具备路由元数据
→ completion()时可以直接将RouteMeta属性设置到Postcard
→ 避免了postcard.getRouteMeta().getDestination()的嵌套调用
→ 简化了后续代码中对路由信息的访问
// LogisticsCenter.completion()中的属性赋值
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
// 继承使得这些setter操作自然流畅
2. RouteMeta是编译时数据,Postcard是运行时数据
→ RouteMeta由APT生成,存储静态路由信息
→ Postcard在导航时创建,附加动态参数
→ 继承体系让静态信息和动态信息融为一体
→ 一个Postcard对象包含了导航所需的所有信息
3. Postcard的链式API设计
每个with方法返回this(Postcard类型):
public Postcard withString(String key, String value) {
mBundle.putString(key, value);
return this;
}
→ 流式API,代码可读性好
→ 所有参数配置和路由信息在同一个对象上
→ navigation()方法在Postcard上直接调用
4. greenChannel的设计
Provider和Fragment类型自动设置greenChannel=true
→ 跳过拦截器,因为它们不是页面跳转
→ 用户也可以手动调用greenChannel()跳过拦截器
→ 如:跳转到关于页面、设置页面等不需要登录检查的页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# 8.3 Warehouse单例仓库核心设计
// Warehouse — ARouter的核心数据仓库
class Warehouse {
// Group索引:group名 → Group加载类
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
// 路由表:path → RouteMeta(按需加载)
static Map<String, RouteMeta> routes = new HashMap<>();
// Provider索引:Provider接口Class → RouteMeta
static Map<Class, RouteMeta> providersIndex = new HashMap<>();
// Provider缓存:Provider接口Class → Provider实例
static Map<Class, IProvider> providers = new HashMap<>();
// 拦截器索引:priority → 拦截器类
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex =
new UniqueKeyTreeMap<>("...");
// 拦截器实例列表(已按priority排序)
static List<IInterceptor> interceptors = new ArrayList<>();
static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Warehouse设计深度分析:
1. 为什么全部使用static字段?
→ Warehouse作为全局唯一的数据仓库
→ 静态字段保证了进程内唯一性
→ 不需要实例化,任何地方直接访问
→ 配合ARouter的单例模式,整个框架只有一份数据
2. 双Map设计(providers vs providersIndex)
providersIndex: Class<IProvider接口> → RouteMeta
→ 存储接口到路由元数据的映射
→ 用于通过接口类型查找Provider
providers: Class<IProvider接口> → IProvider实例
→ 缓存已实例化的Provider
→ 避免重复创建(单例效果)
查找流程:
navigation(IUserService.class)
→ 先查providers缓存
├── 命中 → 直接返回实例
└── 未命中 → 从providersIndex找到RouteMeta
→ 实例化Provider → init() → 存入providers → 返回
3. UniqueKeyTreeMap的精妙选择
拦截器使用UniqueKeyTreeMap存储:
→ TreeMap保证按Key(priority)自然排序
→ Unique保证不允许相同priority
→ 如果两个拦截器priority相同 → 抛异常
→ 编译时/运行时及早发现配置错误
4. groupsIndex的"加载后删除"策略
// 加载完group后从索引中移除
Warehouse.groupsIndex.remove(postcard.getGroup());
为什么?
→ 已加载的group不需要再次加载
→ 删除后下次直接从routes中查找
→ 节省内存(Class对象引用)
→ completion()中用是否存在于groupsIndex判断是否需要加载
5. clear()方法的防御设计
→ 在ARouter.destroy()时调用
→ 清空所有数据,释放引用
→ 防止内存泄漏(尤其是Provider持有Context时)
→ 应用退出时应该调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 8.4 LogisticsCenter路由补全算法
LogisticsCenter(物流中心)是ARouter的核心调度类,
completion()方法是其最重要的算法:
LogisticsCenter.completion(Postcard postcard):
Step 1: 查找路由
RouteMeta routeMeta = Warehouse.routes.get(path);
Step 2: 路由不存在 → 尝试懒加载
if (routeMeta == null) {
Class<? extends IRouteGroup> groupMeta =
Warehouse.groupsIndex.get(group);
if (groupMeta == null) {
// group也不存在 → 路由确实不存在
// 尝试降级处理
if (degradeService != null) {
degradeService.onLost(context, postcard);
}
return;
}
// 懒加载该group的所有路由
groupMeta.getConstructor().newInstance().loadInto(Warehouse.routes);
// 从索引中移除已加载的group
Warehouse.groupsIndex.remove(group);
// 递归重新completion(这次routes中一定有了)
completion(postcard);
}
Step 3: 路由存在 → 填充Postcard
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
...
Step 4: 类型特殊处理
switch (routeMeta.getType()) {
case PROVIDER:
// Provider类型:实例化并缓存
Class<? extends IProvider> providerMeta =
(Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (instance == null) {
instance = providerMeta.getConstructor().newInstance();
instance.init(mContext);
Warehouse.providers.put(providerMeta, instance);
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider不经过拦截器
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment也不经过拦截器
break;
}
算法设计亮点:
1. 递归补全的优雅设计
completion() → 发现需要懒加载 → 加载 → 递归调用自己
→ 第二次调用时routes中已有数据 → 正常流程
→ 避免了代码重复(加载后不需要复制同样的填充逻辑)
→ 递归深度最多2层,不会栈溢出
2. 懒加载的触发时机
不是在init()时加载所有路由
→ 而是在首次导航到某个group时才加载该group
→ 典型的延迟初始化策略
→ 对包含数百个路由的大型App尤为重要
3. 降级策略的优先级
路由不存在时:
→ 先检查DegradeService(全局降级)
→ NavigationCallback.onLost()(单次降级)
→ 两者可以共存,单次优先
4. Provider的双重缓存
→ 先查Warehouse.providers(实例缓存)
→ 未命中才实例化
→ init()只调用一次
→ 保证Provider的单例语义
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 8.5 拦截器CancelableCountDownLatch设计
// CancelableCountDownLatch — ARouter自定义的可取消倒计时锁
// 继承自CountDownLatch,增加了cancel能力
public class CancelableCountDownLatch extends CountDownLatch {
public CancelableCountDownLatch(int count) {
super(count);
}
// 取消等待,立即释放所有等待线程
public void cancel() {
while (getCount() > 0) {
countDown(); // 将计数器减到0
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
CancelableCountDownLatch在拦截器链中的应用:
InterceptorServiceImpl.doInterceptions():
CancelableCountDownLatch counter = new CancelableCountDownLatch(1);
// 在子线程异步执行拦截器链
_execute(0, counter, postcard);
// 主导航线程等待拦截器链完成
counter.await(timeout, TimeUnit.SECONDS);
if (counter.getCount() > 0) {
// 超时未完成 → 导航超时
}
三种结束方式:
1. 所有拦截器放行 → 最后一个拦截器后countDown() → await返回
2. 某个拦截器中断 → cancel() → countDown到0 → await返回
3. 超时 → await自动返回 → getCount() > 0 标识超时
设计优势分析:
1. 为什么用CountDownLatch而不是wait/notify?
→ CountDownLatch语义更清晰:等待一个事件完成
→ 自带超时机制:await(timeout, unit)
→ 不会出现虚假唤醒问题
→ 一次性使用,不需要重置
2. 为什么需要cancel()方法?
标准CountDownLatch没有cancel
→ 拦截器中断时需要立即唤醒等待线程
→ cancel()通过循环countDown实现
→ 比Thread.interrupt()更温和安全
3. 为什么计数器是1而不是拦截器数量?
→ 拦截器链是串行的(异步递归执行)
→ 不是并行等待多个拦截器完成
→ 只需要等待整条链的最终结果
→ count=1表示"等待链执行完毕"这一个事件
4. 超时机制的重要性
→ 拦截器在子线程中异步执行
→ 如果某个拦截器既不onContinue也不onInterrupt → 卡住
→ 超时机制保证导航不会永远阻塞
→ 默认300秒(可通过Postcard.setTimeout()配置)
→ 这是防御性编程的典型案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 8.6 JavaPoet代码生成设计
ARouter使用JavaPoet库在编译时生成Java代码,
这是APT处理器中代码生成的最佳实践:
JavaPoet核心API:
TypeSpec → 生成类定义
MethodSpec → 生成方法定义
FieldSpec → 生成字段定义
CodeBlock → 生成代码块
ParameterSpec → 生成参数定义
JavaFile → 写入文件
RouteProcessor中的代码生成示例:
// 生成loadInto方法
MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(
ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class)
),
"atlas"
);
// 为每个路由生成注册代码
for (RouteMeta routeMeta : routeMetaList) {
loadIntoMethodBuilder.addStatement(
"atlas.put($S, $T.build($T.$L, $T.class, $S, $S, null, $L, $L))",
routeMeta.getPath(),
ClassName.get(RouteMeta.class),
ClassName.get(RouteType.class),
routeMeta.getType(),
ClassName.get(routeMeta.getDestination()),
routeMeta.getPath(),
routeMeta.getGroup(),
routeMeta.getPriority(),
routeMeta.getExtra()
);
}
// 生成类
TypeSpec typeSpec = TypeSpec.classBuilder(groupClassName)
.addSuperinterface(ClassName.get(IRouteGroup.class))
.addModifiers(Modifier.PUBLIC)
.addMethod(loadIntoMethodBuilder.build())
.build();
// 写入文件
JavaFile.builder(ROUTE_PACKAGE, typeSpec)
.build()
.writeTo(mFiler);
JavaPoet设计优势:
1. 类型安全:通过ClassName、TypeName等避免拼字符串
→ 不会出现拼错类名、包名的问题
→ 重构时IDE可以追踪引用
2. 自动import管理
→ JavaFile自动收集所有引用的类型
→ 自动生成import语句
→ 不会遗漏或重复
3. $S/$T/$L占位符
→ $S: 字符串字面量(自动加引号)
→ $T: 类型引用(自动import)
→ $L: 字面量(原样输出)
→ 简洁且不易出错
4. 相比直接拼StringBuilder
→ 可读性好:代码意图清晰
→ 维护性好:修改一处自动影响关联部分
→ 正确性好:格式、缩进自动处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 8.7 两级索引架构思想
ARouter的两级索引是其性能优化的核心设计:
一级索引(Group Index) — init时加载
groupsIndex: {
"user" → ARouter$$Group$$user.class
"shop" → ARouter$$Group$$shop.class
"home" → ARouter$$Group$$home.class
"setting" → ARouter$$Group$$setting.class
}
→ 数据量很小(只有group数量,通常10-20个)
→ Class对象引用,内存占用极小
→ O(1)查找
二级索引(Routes) — 按需加载
routes: {
"/user/login" → RouteMeta(ACTIVITY, LoginActivity.class, ...)
"/user/register" → RouteMeta(ACTIVITY, RegisterActivity.class, ...)
"/user/detail" → RouteMeta(ACTIVITY, UserDetailActivity.class, ...)
// shop和home的路由还未加载...
}
→ 只有被使用到的group的路由才会加载
→ 每个RouteMeta包含完整路由信息
→ 加载后的路由O(1)查找
性能分析(假设应用有200个路由,20个group):
全量加载方式:
init()时加载200个RouteMeta → 创建200个对象 → 200次Map.put
内存:200 × RouteMeta对象大小
时间:遍历所有路由注册类 + 200次put
两级索引方式:
init()时只加载20个Group索引 → 20个Class引用 → 20次Map.put
导航到某个group时才加载该group的路由(平均10个)
如果用户只访问了3个group:
→ 实际加载:20 + 3×10 = 50个Map条目
→ 比全量加载减少75%
类比理解:
全量加载 = 开机时把整本字典读入内存
两级索引 = 开机时只加载目录,翻到某页时才读该页内容
→ 大幅降低初始化时间和内存占用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 8.8 _ARouter门面模式设计
ARouter与_ARouter的双层设计是门面模式的典型应用:
外层:ARouter(公开API,对用户可见)
public final class ARouter {
private volatile static ARouter instance = null;
private volatile static boolean hasInit = false;
public static void init(Application application) {
if (!hasInit) {
_ARouter.init(application);
hasInit = true;
}
}
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
public <T> T navigation(Class<? extends T> service) {
return _ARouter.getInstance().navigation(service);
}
}
内层:_ARouter(实际实现,包级别访问)
final class _ARouter {
// 所有真正的逻辑都在这里
static void init(Application application) { ... }
protected Postcard build(String path) { ... }
protected Object navigation(Context ctx, Postcard postcard, ...) { ... }
}
这种双层设计的价值:
1. 接口稳定性
→ ARouter的公开方法是稳定的API
→ _ARouter的内部实现可以随时重构
→ 只要ARouter的接口不变,所有使用者不受影响
2. 访问控制
→ ARouter是public的,用户可以直接使用
→ _ARouter是包级别的,用户无法直接访问
→ 防止用户绕过ARouter直接操作内部逻辑
→ 内部类、内部方法不会出现在IDE的自动补全中
3. 日志和异常的统一处理
ARouter在委托调用时可以统一添加:
→ try-catch包裹(捕获内部异常,友好提示)
→ 日志记录(debug模式下输出导航信息)
→ 线程安全检查(init必须在主线程等)
4. 初始化状态管理
→ hasInit在ARouter层管理
→ 所有API调用前检查初始化状态
→ 未初始化时给出清晰的错误信息
→ _ARouter可以假设已初始化,简化内部逻辑
这种设计模式在Android开发中很常见:
→ Glide(Glide vs RequestManager)
→ Retrofit(Retrofit vs ServiceMethod)
→ 对外简洁,对内复杂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 8.9 Provider单例缓存设计
Provider的单例缓存是ARouter中服务发现的关键设计:
Provider生命周期管理:
1. 首次获取
navigation(IUserService.class)
→ LogisticsCenter.completion()
→ 检查Warehouse.providers是否有缓存
→ 没有 → 反射实例化 → 调用init(context) → 存入缓存
2. 后续获取
navigation(IUserService.class)
→ LogisticsCenter.completion()
→ 从Warehouse.providers获取缓存实例 → 直接返回
3. 销毁
ARouter.destroy() → Warehouse.clear() → providers.clear()
单例设计的深度思考:
疑惑:Provider为什么默认是单例的?
答疑:
1. 服务通常是无状态的工具类
→ IUserService.getUserInfo()
→ IPayService.pay()
→ 不需要多个实例
2. 避免重复初始化的开销
→ init(context)可能包含耗时操作
→ 如初始化数据库连接、网络配置等
→ 单例避免了重复初始化
3. 与Android Service的语义一致
→ Android的Service也是单例的
→ Provider在概念上等同于"轻量级服务"
→ 保持了语义一致性
Provider初始化的线程安全问题:
// LogisticsCenter中的Provider实例化代码
IProvider provider = Warehouse.providers.get(providerMeta);
if (provider == null) {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
}
潜在问题:这段代码不是线程安全的
→ 两个线程同时navigation同一个Provider
→ 可能创建两个实例,只有一个被缓存
→ init()可能被调用两次
实际影响:
→ 因为completion()在navigation()中有synchronized保护
→ 大多数场景下不会出现并发问题
→ 但在极端场景下仍需注意
→ 这也是ARouter的一个设计瑕疵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 8.10 设计思想总结
ARouter核心设计思想归纳:
1. 编译时 vs 运行时的权衡
APT在编译时生成代码 → 运行时零反射开销
Gradle Plugin在编译时扫描 → 运行时无需遍历dex
→ 核心思想:把能在编译时做的工作都在编译时做完
→ 运行时只做查表和分发,效率极高
2. 分层解耦架构
ARouter(API层)→ _ARouter(实现层)→ Warehouse(数据层)
→ 每层职责清晰
→ 对外API稳定,内部实现可自由演化
→ 门面模式降低了使用复杂度
3. 懒加载优先的策略
路由表分组懒加载 → 按需加载
Provider延迟实例化 → 首次使用时创建
拦截器init在框架init时完成 → 唯一的例外,因为拦截器需要全局可用
→ 不到万不得已不加载,不到万不得已不创建
4. 约定优于配置
path的第一段自动作为group名 → 无需手动指定group
Provider注解的path自动关联接口类型 → 无需额外配置
@Autowired的参数名默认与字段名相同 → 减少配置
→ 减少样板代码,提升开发效率
5. 防御性编程
拦截器超时机制 → 防止导航永远阻塞
路由不存在降级策略 → 优雅处理异常
重复注册检测 → 编译时/运行时双重检查
→ 每个环节都有兜底方案
6. 面向接口编程
IProvider → 服务接口下沉到公共模块
IRouteGroup → 路由注册接口
IInterceptor → 拦截器接口
→ 所有扩展点都是接口
→ 框架与使用者通过接口契约交互
7. 关注点分离
RouteProcessor → 只处理路由注解
InterceptorProcessor → 只处理拦截器注解
AutowiredProcessor → 只处理参数注入
LogisticsCenter → 只负责路由查找和补全
InterceptorServiceImpl → 只负责拦截器执行
→ 每个类只做一件事(单一职责原则)
8. URL驱动的松耦合
模块间不依赖具体类 → 依赖URL字符串
URL可以在运行时动态构建 → 支持Deep Link
URL可以在配置中心管理 → 支持动态路由(TheRouter)
→ 从类级别耦合降级为字符串级别耦合
→ 这是ARouter解决组件化通信问题的根本思路
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 09.面试高频问题深度解析
# 9.1 经典面试题
问题1:ARouter的工作原理是什么?
核心原理:APT编译时生成路由表 + 运行时查表导航
1. 编译时
APT扫描@Route注解
→ 为每个模块生成路由注册类(包含path→Class的映射)
2. 初始化时
扫描dex/通过Plugin加载所有路由注册类
→ 将Group索引加载到内存(分组懒加载)
3. 导航时
build(path) → 创建Postcard
navigation() → 根据path查找目标Class
→ 执行拦截器链 → 构建Intent → startActivity()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
问题2:ARouter的拦截器是如何设计的?
责任链模式:
→ 所有拦截器按priority排序
→ 在子线程中依次执行
→ 每个拦截器必须调用onContinue()放行或onInterrupt()中断
→ 有超时机制防止卡住(默认300秒)
→ Fragment跳转默认不经过拦截器(isGreenChannel)
1
2
3
4
5
6
2
3
4
5
6
# 9.2 进阶面试题
问题3:ARouter初始化为什么慢?如何优化?
原因:
默认通过扫描dex文件查找APT生成的路由类
→ 遍历所有类名 → 过滤以特定前缀开头的类
→ 大型项目类数以万计 → 首次扫描耗时1-3秒
优化方案:
1. ARouter自带缓存(第二次启动会用缓存)
2. arouter-register Gradle Plugin
→ 编译时通过ASM字节码插桩
→ 将注册代码直接写入init()方法
→ 运行时无需扫描dex
→ 初始化从秒级降到毫秒级
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
问题4:ARouter的服务发现是如何实现的?
1. 公共模块定义接口(继承IProvider)
2. 业务模块实现接口并添加@Route注解
3. APT生成路由注册代码,将接口→实现类映射存入Warehouse
4. 其他模块通过ARouter.navigation(IService.class)获取实例
5. Provider默认是单例模式,首次获取时实例化并缓存
1
2
3
4
5
2
3
4
5
问题5:为什么路由框架使用APT而不是运行时反射?
APT vs 运行时反射:
APT优势:
├── 编译时生成代码,零运行时开销
├── 编译时可以检查注解错误
├── 生成的代码可以被ProGuard优化
└── 不影响应用启动速度
运行时反射劣势:
├── 启动时需要扫描所有类 → 慢
├── 反射调用性能差
├── 无法在编译时检查错误
└── 不利于代码混淆
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 9.3 学习建议
学习ARouter建议按以下顺序:
- 理解组件化:先理解为什么需要组件化,组件化的核心问题是什么
- 掌握APT:学习注解处理器的工作原理,理解编译时代码生成
- 使用ARouter:在实际项目中使用ARouter,熟悉API
- 阅读源码:从init() → build() → navigation()三条主线阅读源码
- 理解设计:分组懒加载、拦截器责任链、服务发现等设计思想
- 横向对比:了解TheRouter等替代方案,理解各自的优缺点
# 参考博客
- ARouter官方文档
- https://github.com/alibaba/ARouter
- ARouter源码分析
- https://juejin.cn/post/6844903560826060814
上次更新: 2026/06/10, 11:13:41