一、 ARouter 是什么?
ARouter 是阿里巴巴开源的一个 用于帮助 Android App 进行组件化改造的框架 ,主要提供 页面路由 、服务发现 和 参数传递 的核心能力。它解决了传统 Android 开发中存在的几个痛点:
- 显式 Intent 的强依赖:
startActivity(new Intent(CurrentActivity.this, TargetActivity.class))
导致当前模块必须依赖目标 Activity 所在的模块,破坏了模块间的隔离性。 - 隐式 Intent 的繁琐与不灵活: 需要配置复杂的
<intent-filter>
,难以统一管理和维护,参数传递受限且类型不安全。 - 跨模块调用困难: 模块间需要调用服务或方法时,通常需要依赖接口的具体实现类,同样导致强依赖。
- URL Scheme 的管理: 手动管理 H5、Native、第三方 App 跳转的 URL 规则容易出错且难以维护。
- 组件化支持不足: 在组件化架构中,基础模块、业务模块、主 App 之间的解耦和通信需要一套高效、统一的机制。
ARouter 的核心目标就是解耦! 它让不同模块的页面、服务能够在不直接依赖对方实现的情况下进行交互。
二、 ARouter 的核心应用场景
-
页面跳转 (Navigation):
- 模块内跳转: 虽然模块内可以直接用显式 Intent,但使用 ARouter 可以统一跳转方式,代码风格一致。
- 跨模块跳转: 最主要场景! App 模块化后,业务模块 A 需要跳转到业务模块 B 的页面,但模块 A 不应该依赖模块 B。ARouter 通过路径
/moduleb/page
完成跳转。 - 降级策略: 当目标页面不存在(如模块未集成、页面被移除)时,可以跳转到一个统一的错误页、H5 页或友好提示页。
- 携带复杂参数: 支持基本类型、String、Parcelable、Serializable 对象,甚至支持 JSON 字符串自动解析成对象(需配合注解)。
- 跳转拦截: 在跳转过程中插入逻辑,如登录检查、权限验证、埋点统计、参数预处理等。
-
服务调用 (Service Discovery):
- 跨模块 API 调用: 模块 A 需要调用模块 B 提供的某个服务接口(如用户信息服务
IUserService
)。模块 A 只需依赖接口定义(通常放在公共基础模块),通过 ARouter 获取接口的实现(由模块 B 提供),无需依赖模块 B 的具体实现类。实现模块间解耦。 - 实现依赖注入 (DI): 通过
@Autowired
注解,ARouter 可以自动注入其他模块提供的服务实例到字段中。
- 跨模块 API 调用: 模块 A 需要调用模块 B 提供的某个服务接口(如用户信息服务
-
统一链接跳转 (URL Dispatch):
- H5 跳 Native: WebView 中的链接(如
scheme://host/path?key=value
)可以被 ARouter 拦截并映射到对应的 Native 页面。 - 推送/通知跳转: 推送消息中的链接可以统一由 ARouter 处理,跳转到指定页面。
- Deep Link: 从外部(如浏览器、其他 App)通过特定格式的 URL 直接打开 App 内指定页面。
- 运营活动路由: 动态配置的活动落地页 URL,可灵活路由到不同的 Native 或 H5 页面。
- H5 跳 Native: WebView 中的链接(如
-
模块解耦与组件化:
- 基础工程支撑: ARouter 是实现 Android 组件化架构的关键基础设施之一,使业务模块能够真正独立开发、编译、测试和运行(通过配置按需加载依赖)。
- 通信总线: 为模块间通信提供了一套标准、高效的协议。
三、 ARouter 的详细使用方法
1. 添加依赖 (Gradle)
在项目的根 build.gradle
中添加仓库和插件依赖:
groovy
buildscript {
repositories {
google()
jcenter()
mavenCentral() // 确保有这个仓库
}
dependencies {
classpath 'com.android.tools.build:gradle:7.x.x' // 使用你实际的AGP版本
// 添加 ARouter 的注册插件 (最新版可能已集成,请参考官方文档)
classpath "com.alibaba:arouter-register:?" // 检查最新版本号,如 1.0.2
}
}
在 模块 的 build.gradle
(通常是 app 模块和需要提供/使用路由的模块) 中添加依赖和配置:
groovy
android {
defaultConfig {
...
// ARouter 配置
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()] // 每个模块使用自己的模块名
// 如果使用 Kotlin,可能需要添加 kapt 的 arguments (参考官方文档)
}
}
}
...
}
dependencies {
// ARouter 核心库
implementation 'com.alibaba:arouter-api:1.5.2' // 检查最新版本号
// ARouter 注解处理器 (编译时生成代码)
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2' // 检查最新版本号
// 如果使用 Kotlin,用 kapt 替代 annotationProcessor
// kapt 'com.alibaba:arouter-compiler:1.5.2'
}
注意: AROUTER_MODULE_NAME
对于每个模块必须是唯一的,通常使用模块名(如 app
, module_user
, module_order
)。这是 ARouter 实现分组加载的关键。
2. 初始化 (Application)
在 Application
类的 onCreate()
方法中进行 ARouter 的初始化:
java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 这两行必须写在 init 之前,否则这些配置在 init 过程中将无效
if (BuildConfig.DEBUG) { // 这些配置建议在 Debug 模式下开启
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式 (如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
// ARouter.printStackTrace(); // 打印堆栈信息 (可选,调试时更详细)
}
// 尽可能早地初始化 ARouter
ARouter.init(this);
}
}
重要: 在 AndroidManifest.xml
中注册你的 Application
类。
3. 添加页面/服务路由 (使用 @Route
注解)
(1) 声明页面路由
在目标 Activity
或 Fragment
上添加 @Route
注解:
java
@Route(path = "/test/activity") // path 至少需要两级,如 "/xx/yy"
public class TestActivity extends AppCompatActivity {
// ...
}
(2) 声明服务路由 (接口实现类)
首先定义一个接口(通常在公共基础模块):
java
// 在 common 模块定义
public interface HelloService extends IProvider {
String sayHello(String name);
}
然后在提供该接口实现的模块中,编写实现类并添加 @Route
注解:
java
@Route(path = "/service/hello", name = "hello service") // path 标识该服务
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "Hello, " + name + "!";
}
@Override
public void init(Context context) {
// 服务初始化,可选
}
}
4. 发起路由跳转 (页面跳转 & 服务获取)
(1) 基本页面跳转 (不带参数)
java
// 简单跳转
ARouter.getInstance().build("/test/activity").navigation();
// 在 Activity 中使用 (可以获取 onActivityResult)
ARouter.getInstance().build("/test/activity")
.navigation(this, REQUEST_CODE); // REQUEST_CODE 是 int 常量
// 添加转场动画
ARouter.getInstance().build("/test/activity")
.withTransition(R.anim.slide_in_right, R.anim.slide_out_left)
.navigation();
(2) 带参数跳转 (使用 withXxx()
方法)
java
ARouter.getInstance().build("/test/activity")
.withString("key1", "value") // String 参数
.withInt("key2", 666) // int 参数
.withBoolean("key3", true) // boolean 参数
.withParcelable("key4", parcelableObject) // Parcelable 对象
.withSerializable("key5", serializableObject) // Serializable 对象
.withObject("key6", customObject) // 使用自定义 SerializeService (需实现)
.navigation();
在目标页面获取参数 (两种方式):
方式一:手动获取 (通过 Intent)
java
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String value1 = getIntent().getStringExtra("key1");
int value2 = getIntent().getIntExtra("key2", 0);
// ... 获取其他参数
}
}
方式二:自动注入 (推荐,使用 @Autowired
注解)
java
@Route(path = "/test/activity")
public class TestActivity extends AppCompatActivity {
@Autowired // 自动注入,name 指定 key, 不指定则默认使用字段名
String key1;
@Autowired(name = "key2") // 指定 key 名
int age;
@Autowired
User user; // 支持自定义对象 (需实现 SerializeService 或使用 JSON 转换)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 必须调用注入方法
ARouter.getInstance().inject(this); // 将传递过来的参数自动注入到 @Autowired 字段
// 现在可以直接使用 key1, age, user 了
Log.d("Test", "key1: " + key1 + ", age: " + age + ", user: " + user);
}
}
注意: 对于 @Autowired
注解的自定义对象,需要在初始化时注册自定义的 JsonService
实现(如果使用 JSON 传递)或者实现 SerializeService
接口。ARouter 默认提供了对基本类型、String、Parcelable、Serializable 的支持。
(3) 获取服务 (服务发现)
java
// 方式 1 (推荐):通过接口类型查找
HelloService helloService = ARouter.getInstance().navigation(HelloService.class);
if (helloService != null) {
String result = helloService.sayHello("ARouter");
}
// 方式 2:通过 Path 查找
HelloService helloService = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
if (helloService != null) {
String result = helloService.sayHello("ARouter");
}
// 方式 3:依赖注入 (在字段上使用 @Autowired)
public class SomeClass {
@Autowired // 自动注入,name 指定 path, 不指定则默认使用字段类型查找
HelloService helloService;
public SomeClass() {
ARouter.getInstance().inject(this); // 必须在构造后调用注入
// 之后就可以直接使用 helloService.sayHello(...) 了
}
}
5. 使用拦截器 (Interceptor
)
拦截器用于在跳转过程中插入自定义逻辑(AOP思想)。
(1) 实现拦截器
创建一个类实现 IInterceptor
接口,并添加 @Interceptor
注解(指定优先级,数字越小优先级越高):
java
@Interceptor(priority = 8, name = "登录拦截器") // priority 很重要,决定执行顺序
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String path = postcard.getPath();
// 判断是否需要登录 (可以根据 path 或其他规则)
if (path.startsWith("/need_login/") && !isUserLoggedIn()) {
// 未登录,中断跳转
callback.onInterrupt(new RuntimeException("用户未登录"));
// 通常在这里跳转到登录页面 (注意避免循环拦截)
ARouter.getInstance().build("/login/activity")
.withString("target_path", path) // 把目标页面的 path 传过去,登录成功后跳回来
.navigation();
} else {
// 继续执行跳转 (放行)
callback.onContinue(postcard);
}
}
@Override
public void init(Context context) {
// 拦截器初始化 (只会调用一次)
}
private boolean isUserLoggedIn() {
// 检查登录状态的逻辑
return ...;
}
}
(2) 拦截器的执行流程
- 当调用
navigation()
时,ARouter 开始处理路由请求。 - 根据优先级 (
priority
) 从高到低 依次执行所有注册的拦截器的process()
方法。 - 在每个拦截器的
process()
中:- 调用
callback.onContinue(postcard)
表示放行,执行下一个拦截器(或最终跳转)。 - 调用
callback.onInterrupt(Throwable)
表示中断路由流程,不会执行后续拦截器,也不会跳转目标页面。navigation()
方法会返回null
(对于服务获取) 或不会打开页面(对于页面跳转),并触发onLost
回调(如果设置了)。
- 调用
- 如果所有拦截器都放行,则最终执行目标页面的跳转或服务的获取。
6. 降级策略 (DegradeService
)
当 ARouter 找不到目标路由(路径不存在、目标模块未加载等)时,会触发降级策略。
(1) 实现降级服务
创建一个类实现 DegradeService
接口,并添加 @Route
注解:
java
@Route(path = "/degrade/global")
public class GlobalDegradeService implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
// 当找不到目标路由时调用
// 可以跳转到一个统一的错误页面、H5 页面或显示 Toast
Intent intent = new Intent(context, NotFoundActivity.class);
intent.putExtra("path", postcard.getPath());
context.startActivity(intent);
}
}
(2) 使用降级策略
-
全局降级: 如上所示,实现
DegradeService
并注册后,所有未找到的路由都会触发onLost
。 -
单次跳降解级: 在
build().navigation()
时,可以单独设置回调来处理丢失情况:javaARouter.getInstance().build("/unknown/path") .navigation(this, new NavigationCallback() { @Override public void onFound(Postcard postcard) { // 找到目标 } @Override public void onLost(Postcard postcard) { // 未找到目标,处理单次丢失 } @Override public void onArrival(Postcard postcard) { // 成功到达目标 (对于页面跳转,是 onActivityResult 之前;对于服务获取,是获取成功时) } @Override public void onInterrupt(Postcard postcard) { // 被拦截器中断 } });
7. 处理 Fragment 路由
ARouter 也支持获取 Fragment 实例。
(1) 声明 Fragment 路由
在目标 Fragment
类上添加 @Route
注解:
java
@Route(path = "/test/fragment")
public class TestFragment extends Fragment {
// ...
}
(2) 获取 Fragment 实例
java
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
// 或者使用类型安全的获取方式 (如果知道具体类型)
TestFragment testFragment = (TestFragment) ARouter.getInstance().build("/test/fragment").navigation();
// 然后使用 FragmentTransaction 添加这个 fragment
8. 自动注入 (@Autowired
) 的深入使用
- 必传参数:
@Autowired(required = true)
(默认)。如果 required=true 的参数没有传递,ARouter 在调用inject()
时会抛出异常。 - 可选参数:
@Autowired(required = false)
。没有传递时,字段保持其初始值(基本类型为默认值如 0/false,对象为 null)。 - 自定义解析: 对于复杂类型或需要特殊处理的类型,可以实现
SerializationService
接口并注册,或者在字段上使用@Autowired
时指定解析器。
9. 混淆配置 (ProGuard)
在 proguard-rules.pro
文件中添加 ARouter 的混淆规则:
kotlin
# ARouter
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe {*;}
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
-keep interface * implements com.alibaba.android.arouter.facade.template.IInterceptor
-keepclassmembers class * {
@com.alibaba.android.arouter.facade.annotation.Autowired <fields>;
}
# 如果使用了自动注入的自定义对象,需要保留其类
# -keep class your.package.model.** { *; }
四、 ARouter 的底层原理
ARouter 的核心原理是 编译时注解处理 (APT) + 运行时查找 + 动态代理。关键在于如何在编译时收集所有路由信息,并在运行时高效地根据路径查找到目标。
1. 编译时处理 (arouter-compiler
)
- 扫描注解: 注解处理器 (
arouter-compiler
) 在编译时会扫描项目中所有被@Route
,@Interceptor
,@Autowired
等 ARouter 注解标记的类(Activity, Fragment, Service 实现类,拦截器等)。 - 生成映射表: 对于扫描到的元素,注解处理器会生成对应的 Java 文件。这些文件主要包含:
ARouter$$Group$$<groupname>
: 这是核心。@Route(path = "/group/activity")
中的group
会被提取出来。每个分组 (group) 会生成一个类,这个类实现了IRouteGroup
接口。它的loadInto(Map<String, RouteMeta> atlas)
方法会将属于该分组的所有路由信息(路径path
-> 路由元信息RouteMeta
)加载到一个 Map 中。RouteMeta
包含了目标 Class、路由类型(Activity/Service/Provider/Fragment)、优先级、额外参数等。ARouter$$Root$$<modulename>
: 每个模块(根据AROUTER_MODULE_NAME
区分)会生成一个根类,实现了IRouteRoot
接口。它的loadInto(Map<String, Class<? extends IRouteGroup>> routes)
方法负责将 该模块中定义的所有分组名 (group
) -> 对应分组加载类的 Class 对象 加载到一个 Map 中。这样运行时只需要知道模块名和分组名,就能找到加载该分组路由的类。ARouter$$Providers$$<modulename>
: 存储该模块中所有实现了IProvider
接口的服务类信息(接口 Class -> 实现类 Class)。ARouter$$Interceptors$$<modulename>
: 存储该模块中定义的所有拦截器信息(拦截器 Class -> 优先级)。<ActivityName>$$ARouter$$Autowired
: 对于被@Autowired
注解的 Activity/Fragment/Service,会生成一个辅助类(实现了ISyringe
接口)。这个类在运行时会被调用,负责将 Intent 或 Bundle 中的参数值自动注入到被@Autowired
标记的字段中。这就是ARouter.getInstance().inject(this)
背后发生的事情。
- 增量编译: 注解处理器支持增量编译,只处理发生变化的文件,提高编译速度。
2. 运行时初始化 (init
)
- 当调用
ARouter.init(this)
时:- 查找所有 Root 类: 利用
DexFile
或ClassLoader
扫描dex
文件(或特定包名com.alibaba.android.arouter.routes
),找到所有类名符合ARouter$$Root$$
模式的类(即各个模块生成的IRouteRoot
实现类)。 - 加载分组索引: 实例化这些 Root 类,调用它们的
loadInto
方法。将所有模块上报的 分组名 -> 分组加载类 (ARouter <math xmlns="http://www.w3.org/1998/Math/MathML"> G r o u p Group </math>Group) 的映射关系加载到内存中的一个全局 Map (Warehouse.groupsIndex
) 中。 - 注意: 此时并不会加载具体的路由信息! 只加载了分组索引。这是 ARouter 性能优化的关键,避免在启动时加载所有路由表导致内存占用过高和初始化缓慢。
- 加载拦截器索引: 类似地,查找并加载所有
ARouter$$Interceptors$$
类,将拦截器信息加载到内存 (Warehouse.interceptorsIndex
)。 - 加载服务提供者索引: 查找并加载所有
ARouter$$Providers$$
类,将接口到实现类的映射加载到内存 (Warehouse.providersIndex
)。
- 查找所有 Root 类: 利用
3. 路由查找与跳转 (build
/navigation
)
build(path)
: 根据传入的路径/group/activity
,首先提取出分组名group
。- 查找分组:
- 检查内存缓存 (
Warehouse.routes
) 中是否已经有该分组 (group
) 的完整路由表。 - 如果没有,则去
Warehouse.groupsIndex
中查找该分组名对应的分组加载类ARouter$$Group$$group
的 Class。 - 如果找到,则实例化这个类,调用它的
loadInto(Warehouse.routes)
方法。这个方法会将属于group
分组的所有路由信息(path
->RouteMeta
)加载到全局路由表Warehouse.routes
中。这就是按需加载 (懒加载) 的过程。 一旦加载过,该分组的路由信息就常驻内存缓存。
- 检查内存缓存 (
- 查找目标: 在
Warehouse.routes
中根据完整的路径/group/activity
查找对应的RouteMeta
元信息。 - 执行拦截器 (如果存在): 如果找到了目标 (
RouteMeta
),并且配置了拦截器,则按照优先级依次执行所有注册的拦截器的process
方法。拦截器可以中断或继续流程。 - 执行跳转/获取:
- 页面跳转 (Activity/Fragment): 根据
RouteMeta
中的目标 Class (Activity.class
),使用Intent
启动目标 Activity 或获取 Fragment 实例。处理参数传递(通过withXxx()
设置的参数会被放入Intent
)。 - 服务获取 (Provider): 根据
RouteMeta
中的目标 Class (HelloServiceImpl.class
),直接实例化该类并返回(或从缓存获取)。如果目标实现了IProvider
,还会调用其init
方法(如果是首次获取)。
- 页面跳转 (Activity/Fragment): 根据
- 降级处理 (onLost): 如果在任何一步(找不到分组、分组加载类不存在、分组里没有该路径、拦截器中断后没有处理)导致无法找到目标,则触发降级策略 (
DegradeService.onLost
) 或调用NavigationCallback.onLost
。
4. 服务发现的原理
- 编译时索引:
ARouter$$Providers$$<modulename>
类记录了该模块提供的所有服务(接口 Class -> 实现类 Class)。 - 运行时注册: 在
init
阶段,这些映射关系被加载到Warehouse.providersIndex
中。 - 获取服务:
- 当通过
navigation(Class)
(如navigation(HelloService.class)
) 获取服务时:- 直接去
Warehouse.providersIndex
中查找HelloService.class
对应的实现类 Class (HelloServiceImpl.class
)。 - 实例化
HelloServiceImpl
(或从缓存获取),返回给调用者。
- 直接去
- 当通过
build(path).navigation()
获取服务时:- 先按路径查找路由信息 (
RouteMeta
)。 - 从
RouteMeta
中获取目标服务实现类的 Class。 - 实例化并返回。
- 先按路径查找路由信息 (
- 当通过
- 依赖注入 (
@Autowired
): 在调用inject()
时,ARouter 会查找为当前类生成的*$$ARouter$$Autowired
辅助类。这个类实现了ISyringe
接口,其inject
方法知道如何从Intent
或Bundle
中取出值,并通过反射(或更安全的如MethodHandle
)设置到被@Autowired
标记的字段上。对于服务类型的字段,它会通过上述服务发现机制去查找对应的实现并注入。
5. 关键设计思想
- 分组加载 (Group Management): 避免一次性加载所有路由信息到内存,按需加载(懒加载),极大提升性能,特别是路由表庞大时。
- APT 生成代码: 编译时生成映射表代码,避免了运行时反射扫描带来的性能损耗和安全问题(如 Android 8.0+ 对 DexFile 扫描的限制)。
- SPI (Service Provider Interface): 服务发现机制本质上是一种 SPI 实现,通过接口解耦调用方和实现方。
- AOP (面向切面编程): 拦截器机制提供了强大的 AOP 能力,可以在路由跳转的关键节点插入通用逻辑。
- 降级策略: 提供优雅的失败处理机制,增强鲁棒性。
- 解耦: 贯穿始终的设计目标,解决模块间直接依赖问题。
总结
ARouter 是一个功能强大、设计精巧的 Android 路由和服务发现框架。它通过编译时注解处理和运行时按需加载机制,高效地解决了模块间页面跳转、服务调用和解耦的问题。其核心原理在于利用 APT 生成路由、分组、服务、拦截器的映射表代码,并在运行时通过分组懒加载和动态查找来完成路由导航和服务获取。拦截器和降级策略提供了灵活性和鲁棒性。理解其使用方法和底层原理,对于构建大型、组件化的 Android 应用至关重要。