组件化与路由设计
# 18.组件化与路由设计
# 目录介绍
- 一、引言:为什么需要组件化
- 二、单体架构的困境
- 2.1 单体架构的问题
- 2.2 架构演进路线
- 三、组件化架构设计
- 3.1 分层架构
- 3.2 组件通信的核心挑战
- 四、Gradle模块化实现
- 4.1 模块类型配置
- 4.2 依赖管理
- 五、组件间通信方案
- 5.1 方案对比
- 5.2 接口下沉方案
- 六、路由框架的核心原理
- 6.1 路由的本质
- 6.2 路由框架的工作流程
- 七、ARouter源码分析
- 7.1 初始化过程
- 7.2 导航过程
- 7.3 拦截器机制
- 八、APT注解处理器原理
- 8.1 APT的工作机制
- 8.2 RouteProcessor核心逻辑
- 九、组件的独立调试与运行
- 9.1 独立运行配置
- 9.2 Mock与桩服务
- 十、依赖注入与服务发现
- 10.1 ServiceLoader机制
- 10.2 ARouter的Provider机制
- 十一、资源隔离与冲突解决
- 11.1 资源命名冲突
- 11.2 组件间资源共享
- 十二、组件化实战架构设计
- 12.1 完整的项目结构
- 十三、面试高频问题与深度分析
- 13.1 组件化和插件化的区别?
- 13.2 ARouter是如何实现自动注册的?
- 13.3 ARouter的路由是如何实现的?
- 十四、组件化的常见问题与解决方案
- 14.1 组件间通信问题
- 14.2 资源冲突问题
- 14.3 初始化顺序问题
- 十五、总结
# 一、引言:为什么需要组件化
当Android项目代码量超过20万行、开发人员超过10人时,单体架构会带来编译慢、耦合重、冲突多等问题。组件化通过将应用拆分为多个独立模块,解决大型项目的工程管理难题。
疑惑:组件化和模块化有什么区别?路由框架如何实现跨模块的页面跳转?
# 二、单体架构的困境
# 2.1 单体架构的问题
大型单体项目的痛点:
1. 编译速度
单体项目全量编译 → 10分钟+
组件化增量编译 → 30秒(只编译修改的模块)
2. 代码耦合
A功能直接引用B功能的类
→ 修改B可能导致A编译失败
→ 无法独立测试单个功能
3. 协作冲突
多人修改同一个module → git冲突频繁
代码审查困难 → 无法确定修改的影响范围
4. 复用困难
想复用登录模块到另一个App → 无法剥离
强依赖关系 → 牵一发而动全身
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
# 2.2 架构演进路线
单体架构 → 模块化 → 组件化 → 微应用
单体:所有代码在一个module中
模块化:按功能分module,但存在直接依赖
组件化:module间通过接口/路由通信,可独立运行
微应用:每个业务是独立App,通过IPC通信
1
2
3
4
5
6
2
3
4
5
6
架构演进的驱动力和关键特征:
1. 单体架构(10万行以下,5人以下团队)
→ 所有代码一个module
→ 构建快(代码量小时)
→ 问题:代码量增长后编译慢、冲突多、无法并行开发
2. 模块化(10-50万行,5-15人团队)
→ 按功能/业务拆分为多个module
→ 各module之间可以直接依赖
→ 问题:module间耦合严重,A依赖B、B依赖C
→ 修改底层module可能导致大量上层编译失败
3. 组件化(50万行以上,15人以上团队)
→ module间通过接口/路由通信,禁止直接依赖
→ 每个组件可以独立编译、运行、测试
→ 关键技术:路由框架 + 接口下沉 + APT
→ 问题:需要额外的基础设施(路由、服务发现)
4. 微应用(超大规模,如支付宝/美团)
→ 每个业务是独立App/插件
→ 通过IPC/ContentProvider通信
→ 动态加载/卸载业务
→ 问题:架构复杂度高,插件化方案受系统版本限制
选择原则:
不要过度设计 → 项目规模决定架构选择
单体够用就不要组件化 → 增加复杂度有成本
组件化的核心价值 → 编译速度和团队并行开发
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
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
# 三、组件化架构设计
# 3.1 分层架构
组件化分层模型:
┌─────────────────────────────────────────────┐
│ App壳工程(app module) │
│ 只负责组装各业务组件,几乎无业务代码 │
├─────────────────────────────────────────────┤
│ 业务组件层(Business Components) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │登录 │ │首页 │ │商品 │ │订单 │ │
│ │模块 │ │模块 │ │模块 │ │模块 │ │
│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │
│ │ │ │ │ │
│ └───────┴───┬───┴───────┘ │
├─────────────────┼───────────────────────────┤
│ 公共服务层 │ │
│ ┌─────────────┼─────────────┐ │
│ │路由服务 │网络服务 │图片服务 │ │
│ │组件通信 │API管理 │加载缓存 │ │
│ └─────────────┴─────────────┘ │
├─────────────────────────────────────────────┤
│ 基础库层(Base Libraries) │
│ ├── 工具类(StringUtils, DateUtils等) │
│ ├── UI组件(通用Dialog, Toast封装等) │
│ ├── 网络框架(OkHttp/Retrofit封装) │
│ └── 存储框架(SP/Room/MMKV封装) │
└─────────────────────────────────────────────┘
关键规则:
1. 上层可以依赖下层,下层不能依赖上层
2. 同层组件之间不能直接依赖
3. 业务组件间通过路由/接口通信
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
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
# 3.2 组件通信的核心挑战
问题:业务组件A如何调用业务组件B的方法?
直接依赖(错误):
module_a → implementation project(':module_b')
→ 违反了组件独立性原则
正确方案:
1. 路由(页面跳转)
ARouter.getInstance().build("/order/detail").withLong("id", 123).navigation()
2. 接口下沉(服务调用)
→ 接口定义在公共模块
→ 实现在业务模块
→ 通过ServiceLoader/SPI发现实现
3. 事件总线(松耦合通知)
EventBus/LiveData 发送跨模块事件
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
# 四、Gradle模块化实现
# 4.1 模块类型配置
// 组件可以在Application和Library模式间切换
// gradle.properties
isLoginModuleRunAlone=false // true时作为独立App运行
// module_login/build.gradle
if (isLoginModuleRunAlone.toBoolean()) {
apply plugin: 'com.android.application' // 独立运行
} else {
apply plugin: 'com.android.library' // 作为库被依赖
}
android {
defaultConfig {
if (isLoginModuleRunAlone.toBoolean()) {
applicationId "com.example.login" // 独立运行时需要applicationId
}
}
sourceSets {
main {
if (isLoginModuleRunAlone.toBoolean()) {
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
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
# 4.2 依赖管理
// 统一版本管理(versions.gradle或Version Catalog)
// gradle/libs.versions.toml
[versions]
kotlin = "1.9.0"
retrofit = "2.9.0"
room = "2.6.0"
[libraries]
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
// 使用
dependencies {
implementation(libs.retrofit)
implementation(libs.room.runtime)
}
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.1 方案对比
┌──────────────┬────────────┬──────────────┬──────────┐
│ 方案 │ 适用场景 │ 优点 │ 缺点 │
├──────────────┼────────────┼──────────────┼──────────┤
│ 隐式Intent │ 页面跳转 │ 系统原生 │ 不安全 │
│ 路由框架 │ 页面跳转 │ 编译检查、拦截 │ 需要框架 │
│ 接口下沉+SPI │ 服务调用 │ 类型安全 │ 模板代码 │
│ EventBus │ 事件通知 │ 解耦 │ 调试困难 │
│ BroadcastReceiver│广播 │ 系统级 │ 性能差 │
└──────────────┴────────────┴──────────────┴──────────┘
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 5.2 接口下沉方案
// 公共模块(module_common)中定义接口
public interface ILoginService {
boolean isLogin();
UserInfo getCurrentUser();
void startLoginActivity(Context context);
}
// 登录模块(module_login)中实现
@Route(path = "/login/service")
public class LoginServiceImpl implements ILoginService {
@Override
public boolean isLogin() {
return LoginManager.getInstance().isLogin();
}
@Override
public UserInfo getCurrentUser() {
return LoginManager.getInstance().getUser();
}
}
// 其他模块中使用
ILoginService loginService = ARouter.getInstance()
.navigation(ILoginService.class);
if (!loginService.isLogin()) {
loginService.startLoginActivity(this);
}
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
# 六、路由框架的核心原理
# 6.1 路由的本质
路由框架解决的核心问题:
→ 如何在不直接依赖目标类的情况下,通过字符串路径跳转到目标页面
传统方式(有依赖):
Intent intent = new Intent(this, OrderDetailActivity.class);
// 必须能访问OrderDetailActivity类 → 必须依赖order模块
路由方式(无依赖):
ARouter.getInstance().build("/order/detail").navigation();
// 只需要知道路径字符串 → 不需要依赖order模块
路由框架的核心就是维护一个 路径→目标 的映射表
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 6.2 路由框架的工作流程
编译时(APT处理):
1. 扫描所有@Route注解
2. 生成路由表代码(路径 → Activity.class)
运行时(初始化):
3. 加载所有生成的路由表到内存HashMap
运行时(导航):
4. 根据路径查找目标Activity.class
5. 构建Intent并启动
详细流程:
┌──────────────────────────────────┐
│ 编译时 │
│ @Route("/order/detail") │
│ class OrderDetailActivity │
│ ↓ APT │
│ 生成 ARouter$$Route$$order.java │
│ {"/order/detail" → OrderDetail} │
└──────────────────────────────────┘
↓ 打包到APK
┌──────────────────────────────────┐
│ 运行时 │
│ ARouter.init() │
│ → 扫描dex找到所有生成的路由类 │
│ → 加载到 HashMap<String, Class> │
│ │
│ ARouter.build("/order/detail") │
│ → 从HashMap查找 │
│ → 找到OrderDetailActivity.class │
│ → new Intent(ctx, clazz) │
│ → startActivity(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
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
# 七、ARouter源码分析
# 7.1 初始化过程
// ARouter.init() → LogisticsCenter.init()
public static void init(Application application) {
// 1. 找到所有APT生成的路由类
Set<String> routerMap;
if (registerByPlugin) {
// Gradle插件方式(推荐,更快)
// 插件在编译时已经将所有路由类注册到loadRouterMap()方法中
} else {
// 扫描dex方式(兜底方案)
routerMap = ClassUtils.getFileNameByPackageName(
mContext, "com.alibaba.android.arouter.routes");
// 扫描所有包名以arouter.routes开头的类
}
// 2. 反射创建实例并调用loadInto()加载路由表
for (String className : routerMap) {
Class<?> clazz = Class.forName(className);
Object instance = clazz.getConstructor().newInstance();
if (instance instanceof IRouteGroup) {
((IRouteGroup) instance).loadInto(Warehouse.groupsIndex);
} else if (instance instanceof IProviderGroup) {
((IProviderGroup) instance).loadInto(Warehouse.providersIndex);
} else if (instance instanceof IInterceptorGroup) {
((IInterceptorGroup) instance).loadInto(Warehouse.interceptorsIndex);
}
}
}
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
# 7.2 导航过程
// ARouter.build(path).navigation()
// → _ARouter.getInstance().navigation(context, postcard, requestCode, callback)
protected Object navigation(Context context, Postcard postcard, int requestCode, ...) {
// 1. 预处理:补全路由信息
LogisticsCenter.completion(postcard);
// 从Warehouse中查找路径对应的RouteMeta
// 填充destination(目标Class)、type(Activity/Fragment/Provider)等
// 2. 执行拦截器链
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
// 被拦截,不继续导航
}
});
}
private Object _navigation(Postcard postcard, int requestCode, ...) {
switch (postcard.getType()) {
case ACTIVITY:
Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// 在UI线程启动Activity
ActivityCompat.startActivity(context, intent, postcard.getOptionsBundle());
break;
case PROVIDER:
return postcard.getProvider(); // 返回服务实例
case FRAGMENT:
Class<?> fragmentClass = postcard.getDestination();
Fragment fragment = fragmentClass.getConstructor().newInstance();
fragment.setArguments(postcard.getExtras());
return fragment;
}
return null;
}
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
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
# 7.3 拦截器机制
// 拦截器:在导航前执行,可以中断导航
@Interceptor(priority = 8, name = "登录拦截器")
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if (postcard.getExtra() == NEED_LOGIN && !isLogin()) {
// 需要登录但未登录 → 跳转登录页
ARouter.getInstance().build("/login/main").navigation();
callback.onInterrupt(new RuntimeException("need login"));
} else {
callback.onContinue(postcard); // 继续导航
}
}
}
// 拦截器链的执行是按priority排序的
// 优先级小的先执行
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
# 八、APT注解处理器原理
# 8.1 APT的工作机制
APT(Annotation Processing Tool)工作流程:
Java源码 → javac编译器 → APT处理 → 生成新的Java源码 → 继续编译
1. 编译器扫描源码中的注解
2. 调用对应的注解处理器(Processor)
3. 处理器分析注解信息,生成新的Java文件
4. 新生成的文件也参与编译
ARouter的APT处理器:
@Route("/order/detail")
class OrderDetailActivity extends Activity {}
↓ APT处理
// 自动生成
public class ARouter$$Group$$order implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/order/detail",
RouteMeta.build(RouteType.ACTIVITY,
OrderDetailActivity.class, "/order/detail", "order"));
}
}
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
# 8.2 RouteProcessor核心逻辑
@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> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
// 按group分组
Map<String, Set<RouteMeta>> groupMap = new HashMap<>();
for (Element element : routeElements) {
Route route = element.getAnnotation(Route.class);
String path = route.path();
String group = extractGroup(path); // 从路径提取分组:"/order/detail" → "order"
RouteMeta routeMeta = new RouteMeta(route, element);
groupMap.computeIfAbsent(group, k -> new TreeSet<>()).add(routeMeta);
}
// 为每个group生成路由表类
for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
generateRouteGroupFile(entry.getKey(), entry.getValue());
}
return 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
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
# 九、组件的独立调试与运行
# 9.1 独立运行配置
组件独立运行的配置:
module_login/
├── src/
│ ├── main/
│ │ ├── java/ ← 业务代码
│ │ ├── res/ ← 资源
│ │ └── AndroidManifest.xml ← 库模式清单
│ └── debug/ ← 独立运行时额外资源
│ ├── java/
│ │ └── DebugLoginActivity.java ← 调试入口
│ └── AndroidManifest.xml ← 独立运行清单(有Application和启动Activity)
└── build.gradle
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.2 Mock与桩服务
// 组件独立运行时,依赖的其他模块服务不可用
// 需要Mock服务实现
// 在debug目录下提供Mock实现
public class MockUserService implements IUserService {
@Override
public UserInfo getCurrentUser() {
return new UserInfo("test_user", "测试用户");
}
}
// 在独立运行的Application中注册Mock服务
public class DebugApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ServiceRegistry.register(IUserService.class, new MockUserService());
}
}
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
# 十、依赖注入与服务发现
# 10.1 ServiceLoader机制
// Java SPI(Service Provider Interface)在Android中的应用
// 1. 定义接口(module_common)
public interface IPayService {
void pay(PayRequest request, PayCallback callback);
}
// 2. 实现接口(module_pay)
// META-INF/services/com.example.common.IPayService 文件内容:
// com.example.pay.WeChatPayService
public class WeChatPayService implements IPayService {
@Override
public void pay(PayRequest request, PayCallback callback) {
// 微信支付逻辑
}
}
// 3. 发现服务(任意模块)
ServiceLoader<IPayService> loader = ServiceLoader.load(IPayService.class);
for (IPayService service : loader) {
service.pay(request, callback);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 10.2 ARouter的Provider机制
// ARouter的IProvider是更方便的服务发现方案
// 定义(module_common)
public interface IAccountService extends IProvider {
boolean isVIP();
}
// 实现(module_account)
@Route(path = "/account/service")
public class AccountServiceImpl implements IAccountService {
@Override
public void init(Context context) {}
@Override
public boolean isVIP() {
return AccountManager.isVIP();
}
}
// 使用(任意模块)
// 方式1:路径发现
IAccountService service = (IAccountService) ARouter.getInstance()
.build("/account/service").navigation();
// 方式2:类型发现(推荐)
@Autowired
IAccountService accountService;
// 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
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
# 十一、资源隔离与冲突解决
# 11.1 资源命名冲突
问题:不同模块可能有同名资源
module_a: res/layout/activity_main.xml
module_b: res/layout/activity_main.xml
→ 合并时会冲突,后编译的覆盖先编译的
解决方案:资源前缀
android {
resourcePrefix "login_" // 强制资源名以login_开头
}
// login_activity_main.xml
// login_ic_logo.png
// login_color_primary
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 11.2 组件间资源共享
公共资源管理策略:
base_res模块(资源库)
├── colors.xml ← 统一颜色定义
├── dimens.xml ← 统一尺寸定义
├── styles.xml ← 统一主题样式
├── strings.xml ← 通用字符串
└── drawable/ ← 通用图标
所有业务模块 → 依赖base_res
→ 保证视觉一致性
→ 避免资源重复
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 十二、组件化实战架构设计
# 12.1 完整的项目结构
项目结构示例:
app/ ← 壳工程(组装各模块)
├── module_common/ ← 公共接口定义
├── module_base/ ← 基础库(网络、存储、UI组件)
├── module_router/ ← 路由配置
├── module_login/ ← 登录业务
├── module_home/ ← 首页业务
├── module_shop/ ← 商城业务
├── module_order/ ← 订单业务
├── module_mine/ ← 个人中心
└── module_share/ ← 分享组件
依赖关系:
app → [login, home, shop, order, mine]
login/home/shop/order/mine → [common, base, router]
common → [base]
base → [第三方库]
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
# 十三、面试高频问题与深度分析
# 13.1 组件化和插件化的区别?
组件化:
→ 编译时决定模块组合
→ 所有模块在同一个APK中
→ 通过路由/接口通信
→ 目的:工程管理、解耦
插件化:
→ 运行时动态加载模块(单独的APK/dex)
→ 模块可以不在主APK中
→ 通过反射/Hook实现
→ 目的:动态更新、减少包体积
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 13.2 ARouter是如何实现自动注册的?
两种方式:
1. 运行时扫描dex(默认方式)
→ 遍历所有dex文件
→ 找到包名为arouter.routes的类
→ 反射实例化并加载路由表
→ 缺点:首次启动耗时
2. Gradle插件方式(arouter-register)
→ 编译时扫描所有class文件
→ 在LogisticsCenter.loadRouterMap()方法中插入代码
→ 运行时直接调用,无需扫描dex
→ 优点:初始化更快
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
# 13.3 ARouter的路由是如何实现的?
ARouter路由实现原理:
1. 编译时(APT)
├── 扫描所有@Route注解
├── 为每个模块生成路由表类
│ ARouter$$Group$$xxx implements IRouteGroup {
│ void loadInto(Map<String, RouteMeta> atlas) {
│ atlas.put("/app/main", RouteMeta.build(
│ RouteType.ACTIVITY, MainActivity.class, ...));
│ }
│ }
└── 生成的类按模块组织
2. 运行时(初始化)
├── 扫描dex文件找到所有ARouter$$开头的类
├── 加载路由表到内存HashMap
└── 按Group分组,延迟加载
3. 导航时
ARouter.getInstance().build("/app/main").navigation()
├── 查找路由表获取目标Class
├── 依次执行拦截器链
├── 构建Intent
└── startActivity
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
# 十四、组件化的常见问题与解决方案
# 14.1 组件间通信问题
组件化后,模块之间不能直接引用,通信成为核心问题:
组件间通信的五种方案:
1. 路由通信(ARouter/TheRouter)
└── 通过URL路径跳转页面
└── 优点:解耦,支持拦截器
└── 缺点:只支持页面跳转,不支持数据回调
2. 接口下沉(推荐)
└── 将接口定义放在公共模块(base)中
└── 各组件实现接口,通过SPI/路由注册
└── 调用方通过接口获取实现
└── 优点:类型安全,IDE支持好
3. EventBus事件总线
└── 发布-订阅模式
└── 优点:完全解耦
└── 缺点:事件追踪困难,容易滥用
4. ContentProvider
└── 系统级跨进程数据共享方案
└── 优点:标准Android机制
└── 缺点:API较重,适合数据密集场景
5. BroadcastReceiver
└── 应用内广播(LocalBroadcast)
└── 优点:系统支持
└── 缺点:只能传递基本数据类型
推荐组合:路由(页面跳转)+ 接口下沉(服务调用)+ EventBus(事件通知)
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
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
# 14.2 资源冲突问题
资源冲突的场景和解决方案:
问题:多个模块定义了同名资源
module-a: res/layout/item_list.xml
module-b: res/layout/item_list.xml
合并后只保留一个 → 界面显示异常
解决方案1:资源前缀(推荐)
android {
resourcePrefix "modulea_"
}
// 资源必须以"modulea_"开头
// 编译时检查,不符合命名规范则报错
解决方案2:资源包名隔离
不同模块使用不同的包名
资源ID的packageId部分不同
解决方案3:自定义Lint规则
编写Lint规则检查资源命名规范
CI/CD中强制执行
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
# 14.3 初始化顺序问题
组件化的初始化问题:
问题:各组件都有初始化逻辑,如何保证顺序?
方案1:Application中手动初始化
缺点:Application需要知道所有组件,不够解耦
方案2:ContentProvider自动初始化
利用ContentProvider在Application.onCreate之前创建的特性
每个组件提供一个初始化ContentProvider
缺点:启动时创建大量ContentProvider影响性能
方案3:App Startup(Jetpack推荐)
统一的初始化框架
支持声明依赖关系和初始化顺序
合并为单个ContentProvider,减少开销
class NetworkInitializer : Initializer<NetworkClient> {
override fun create(context: Context): NetworkClient {
return NetworkClient.init(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
return listOf(LoggerInitializer::class.java) // 依赖Logger先初始化
}
}
方案4:自定义初始化框架
使用APT扫描各模块的初始化器
在运行时按拓扑排序执行
支持异步初始化和延迟初始化
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
# 十五、总结
组件化知识图谱:
架构设计
├── 分层模型 → app壳/业务层/公共服务/基础库
├── 通信方案 → 路由/接口下沉/事件总线
└── Gradle管理 → 模块切换/依赖管理/资源隔离
路由原理
├── APT → 编译时生成路由表
├── 路由表 → HashMap<路径, 目标Class>
├── 拦截器 → 导航前置处理(登录校验等)
└── 服务发现 → IProvider接口注入
工程实践
├── 独立调试 → Application/Library切换
├── Mock服务 → 隔离测试各模块
├── 资源前缀 → 避免命名冲突
└── 版本管理 → 统一依赖版本
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
上次更新: 2026/06/10, 11:13:41