ARouter 设计思想与实战指南
在 Android 应用发展到一定规模后,团队通常会面临一个棘手的架构决策:组件化(Modularization)。将一个巨型的单体应用拆分为多个独立的业务模块(Module),是保持大型项目开发效率和代码健康度的必由之路。然而,组件化带来的第一个核心问题就是:模块之间如何通信?
传统的 Android 页面跳转依赖 Intent,它要求调用方直接引用目标 Activity 的 Class 对象。一旦 ModuleA 想跳转到 ModuleB 的某个页面,就必须在 Gradle 中声明对 ModuleB 的依赖。随着模块增多,模块间的依赖关系会退化为一张纠缠不清的蛛网,最终回到"伪组件化"的老路上。
ARouter,由阿里巴巴开源,正是为了解决这个问题而生的Android 组件化路由框架。它的核心使命是:通过"路径"(URL-like Path)取代"类引用"(Class Reference),彻底切断模块间的编译期依赖。
本文将详细讲解 ARouter 的设计思想、核心 API 使用方式、以及在真实项目中的实战模式。
一、 为什么需要路由框架
1.1 组件化的困境:模块间的"断联"
在一个理想的组件化架构中,各业务模块之间是编译隔离的——ModuleA 的代码看不见 ModuleB 的任何类。这意味着,以下这行最常见的跳转代码在组件化后将无法编译:
// ModuleA 的代码——编译报错!因为 ModuleA 根本看不见 OrderDetailActivity
Intent intent = new Intent(this, OrderDetailActivity.class);
startActivity(intent);
| 传统方案 | 实现方式 | 致命缺陷 |
|---|---|---|
| 隐式 Intent | 通过 action 字符串匹配 |
无编译期校验,写错 action 只能运行时才能发现;无法传递复杂参数 |
| 反射 | Class.forName("com.xxx.OrderDetailActivity") |
性能差(涉及 ClassLoader 查询);重构时极易遗漏更新字符串,引发 crash |
| 公共 Module 存放路由常量 | 所有路由目标注册在一个公共模块的常量中 | 公共模块会膨胀为"上帝模块";每增加一个页面都要修改公共模块 |
这些方案的本质问题是:它们要么引入了运行时的脆弱性(字符串匹配、反射),要么破坏了组件化的边界(公共模块膨胀)。
1.2 路由框架的核心思想:中间人模式
ARouter 的设计哲学可以用一个比喻来理解:
把一个大型企业想象成一座大楼。传统跳转就像"每个部门都记住了其他所有部门的房间号,直接去敲门"——部门越多,记忆负担越大,换个房间就全乱了。ARouter 的方案是在大厅设一个总服务台(路由表):每个部门入驻时在服务台登记自己的路径和房间号;任何人想找某个部门,只要报出路径(如 "/order/detail"),服务台就帮你找到对应的房间。发起方不需要知道目标的具体位置,目标搬了房间也只需更新服务台的登记。
┌───────────┐ ┌──────────────────┐ ┌───────────┐
│ ModuleA │ "/order/detail" │ ARouter 路由表 │ 查表 → 找到目标 │ ModuleB │
│ (调用方) │ ──────────────────→ │ │ ──────────────────→ │ (目标页) │
│ │ 不引用任何 B 的类 │ path → Class 映射 │ 构造 Intent 跳转 │ │
└───────────┘ └──────────────────┘ └───────────┘
▲
│ 编译期自动注册
@Route(path="/order/detail")
这个模型带来了三个根本性优势:
- 编译期解耦:调用方只依赖一个字符串路径,不需要引用目标的 Class
- 中心化管理:所有路由映射在一处可查、可维护
- 可扩展:在跳转过程中可以插入拦截器(登录检查、权限校验等)
二、 环境配置与初始化
2.1 Gradle 依赖
ARouter 由三个核心工件组成,各司其职:
| 工件 | 作用 | 依赖时机 |
|---|---|---|
arouter-api |
运行时 API,提供 build()、navigation() 等方法 |
implementation |
arouter-compiler |
注解处理器,编译时扫描 @Route 并生成路由映射类 |
kapt / annotationProcessor |
arouter-register |
Gradle 插件,通过字节码插桩自动注册路由表(可选但强烈推荐) | classpath |
标准配置(Kotlin + KSP/KAPT):
// 项目根 build.gradle
buildscript {
dependencies {
// 可选:自动注册插件,避免运行时扫描 Dex
classpath 'com.alibaba:arouter-register:1.0.2'
}
}
// 每个需要 ARouter 的业务模块的 build.gradle
plugins {
id 'kotlin-kapt'
}
// 如果使用了 arouter-register 插件
apply plugin: 'com.alibaba.arouter'
android {
defaultConfig {
kapt {
arguments {
// 每个模块必须声明唯一的模块名,APT 用它来区分不同模块的生成代码
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
}
dependencies {
implementation 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
}
关键细节:AROUTER_MODULE_NAME 为什么是必须的? 在多模块项目中,每个模块的注解处理器都会独立运行,各自生成一份路由映射类。AROUTER_MODULE_NAME 充当"命名空间",使得不同模块生成的类不会冲突。例如,module_order 模块会生成 ARouter$$Root$$module_order,module_user 模块会生成 ARouter$$Root$$module_user。
2.2 SDK 初始化
在 Application.onCreate() 中尽早初始化:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 调试模式下开启日志和调试开关——必须在 init() 之前调用
if (BuildConfig.DEBUG) {
ARouter.openLog() // 打印路由日志
ARouter.openDebug() // 开启调试模式(InstantRun 场景必须开启)
}
// 初始化 ARouter
// 内部会加载所有编译期生成的路由映射表到内存
ARouter.init(this)
}
}
init() 的内部动作: 这一行代码的背后,ARouter 需要完成一个关键任务——将所有模块编译时生成的路由映射类加载到内存中的 Warehouse(路由仓库)。具体的加载机制取决于是否使用了 arouter-register 插件:
init() 内部流程:
│
├── 使用了 arouter-register 插件?
│ ├── 是 → 插件已通过字节码插桩将 register() 调用注入到 LogisticsCenter
│ │ 直接执行,无需任何扫描 → 速度极快 (< 1ms)
│ │
│ └── 否 → 运行时扫描 APK 的所有 Dex 文件
│ 通过包名过滤找到 "com.alibaba.android.arouter.routes" 下的类
│ 逐个反射加载 → 速度较慢 (大型项目可达 100ms+)
三、 核心 API:页面路由
3.1 标记路由目标(@Route)
在目标 Activity 上添加 @Route 注解,声明其路由路径:
/**
* 订单详情页
* path 必须至少包含两级(/group/path),第一级自动作为分组名
*/
@Route(path = "/order/detail")
class OrderDetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_order_detail)
}
}
路径规则解读:
- 路径必须以
/开头,且至少包含两级,如/group/name - 第一级路径段(
order)会被自动提取为分组名(Group) - 同一分组的路由信息会被归入同一个生成类中,实现按需加载——只有当该分组第一次被访问时,才会将该分组的路由表加载进内存
- 你也可以通过
@Route(path = "/order/detail", group = "custom_group")手动指定分组,但通常不推荐
3.2 发起路由跳转
在应用的任何位置,通过路径字符串发起跳转:
// 基础跳转——不传参数
ARouter.getInstance()
.build("/order/detail") // 构建一个 Postcard(路由请求卡片)
.navigation() // 执行跳转
// 携带参数跳转
ARouter.getInstance()
.build("/order/detail")
.withString("orderId", "20240101001")
.withInt("source", 1)
.withParcelable("orderInfo", orderParcelable)
.navigation()
// 通过 URI 跳转(适用于从 H5 或 DeepLink 进入原生页面)
val uri = Uri.parse("arouter://m.example.com/order/detail?orderId=123")
ARouter.getInstance()
.build(uri)
.navigation()
build() + navigation() 这两步的设计意图: 为什么不直接一步 navigate("/order/detail") 搞定?因为 ARouter 采用了Builder 模式:build() 返回一个 Postcard 对象(路由请求的载体),你可以在调用 navigation() 之前,链式地向 Postcard 上附加参数、设置 Flags、配置动画,甚至设置跳转回调。这种"先组装、后发射"的模式提供了极大的灵活性。
3.3 跳转动画与 Flags
ARouter.getInstance()
.build("/order/detail")
.withTransition(R.anim.slide_in_right, R.anim.slide_out_left) // 转场动画
.withFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) // Intent Flags
.navigation(this) // 传入 Context
3.4 获取跳转结果(startActivityForResult)
// 使用 navigation(Activity, requestCode) 重载来获取返回结果
ARouter.getInstance()
.build("/order/create")
.navigation(this, REQUEST_CODE_CREATE_ORDER)
// 在 onActivityResult 中接收结果(与标准用法一致)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_CREATE_ORDER && resultCode == RESULT_OK) {
// 处理返回的订单信息
}
}
3.5 获取 Fragment 实例
ARouter 不仅能跳转 Activity,还能通过路径获取 Fragment 实例——这在组件化架构中非常有用,因为宿主 Activity 可能不知道目标 Fragment 在哪个模块中:
// 在目标模块中定义 Fragment 并标注路由
@Route(path = "/user/profile")
class UserProfileFragment : Fragment() {
// ...
}
// 在宿主模块中通过路径获取 Fragment 实例
val fragment = ARouter.getInstance()
.build("/user/profile")
.withString("userId", "12345")
.navigation() as Fragment
// 将其添加到 Activity 的容器中
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragment)
.commit()
四、 参数自动注入(@Autowired)
4.1 告别手动解析 Intent
传统方式下,目标 Activity 需要手动从 Intent.extras 中逐个取出参数,代码繁琐且容易出错。ARouter 的 @Autowired 注解可以自动完成这个过程:
@Route(path = "/order/detail")
class OrderDetailActivity : AppCompatActivity() {
// ARouter 会自动从 Intent 的 extras 中找到 key 为 "orderId" 的值并赋给这个字段
@Autowired
@JvmField
var orderId: String = ""
// 当 URL 参数名与字段名不同时,可以通过 name 属性指定映射关系
@Autowired(name = "source")
@JvmField
var fromSource: Int = 0
// 支持 Parcelable 对象
@Autowired
@JvmField
var orderInfo: OrderInfo? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 关键:必须调用 inject() 触发自动注入
ARouter.getInstance().inject(this)
// 此时 orderId、fromSource、orderInfo 已经被自动赋值
Log.d("OrderDetail", "订单ID: $orderId, 来源: $fromSource")
}
}
4.2 自动注入的内部机制
@Autowired 的工作原理与路由表生成类似——它也是编译时注解处理,而非运行时反射:
编译期:APT 扫描 @Autowired 标注的字段
↓
生成一个 ISyringe(注射器)实现类:
OrderDetailActivity$$ARouter$$Autowired
运行期:调用 ARouter.getInstance().inject(this)
↓
ARouter 通过类名拼接找到生成的注射器类
↓
调用 inject() 方法,内部代码类似:
target.orderId = target.getIntent().getStringExtra("orderId");
target.fromSource = target.getIntent().getIntExtra("source", 0);
这意味着,参数注入的性能与手写 getIntent().getXxx() 基本一致——因为生成的代码本来就是这些 getter 调用。
4.3 传递自定义对象
当你需要通过 URL 传递自定义对象(不是 Parcelable/Serializable)时,ARouter 提供了 SerializationService 接口来桥接 JSON 序列化:
/**
* JSON 序列化服务实现
* ARouter 在需要反序列化 URL 中的对象参数时,会自动查找并调用此服务
*/
@Route(path = "/service/json")
class JsonSerializationService : SerializationService {
private lateinit var gson: Gson
override fun init(context: Context) {
gson = Gson()
}
override fun <T> json2Object(input: String, clazz: Type): T {
return gson.fromJson(input, clazz)
}
override fun object2Json(instance: Any): String {
return gson.toJson(instance)
}
override fun <T> parseObject(input: String, clazz: Type): T {
return gson.fromJson(input, clazz)
}
}
只需声明一次,整个应用中通过 URL 参数传递自定义对象的场景就全部自动生效了。
五、 拦截器(Interceptor):路由的 AOP
5.1 拦截器的设计动机
在真实项目中,很多页面跳转前都需要做"预检查"——最典型的就是登录态校验:用户点击"我的订单",如果尚未登录,应该先跳到登录页。
传统做法是在每个目标 Activity 的 onCreate 中检查登录态,这导致大量重复代码。ARouter 的拦截器机制提供了一种 AOP(面向切面编程) 风格的解决方案:在路由跳转的"中途",插入统一的检查逻辑。
拦截器就像机场的安检通道——你的"明信片"(Postcard)在被派送到目标页面之前,必须经过一道道安检。任何一道安检发现问题,都可以拦截住这次跳转。
5.2 定义拦截器
/**
* 登录状态检查拦截器
* priority 值越小,优先级越高,越先执行
*/
@Interceptor(priority = 8, name = "登录检查拦截器")
class LoginInterceptor : IInterceptor {
private lateinit var context: Context
/**
* 拦截器初始化——在 ARouter.init() 时被调用,且只调用一次
*/
override fun init(context: Context) {
this.context = context
Log.d("ARouter", "LoginInterceptor 已初始化")
}
/**
* 拦截处理
* @param postcard 当前路由请求的完整信息
* @param callback 控制流:继续或中断
*/
override fun process(postcard: Postcard, callback: InterceptorCallback) {
// 检查目标页面是否需要登录(通过 extras 标记位判断)
if (postcard.extra and NEED_LOGIN != 0) {
if (UserManager.isLoggedIn()) {
// 已登录,放行
callback.onContinue(postcard)
} else {
// 未登录,中断当前跳转,转向登录页
ARouter.getInstance()
.build("/user/login")
.withString("redirect", postcard.path) // 记录原始目标路径
.navigation()
// 中断原始跳转
callback.onInterrupt(RuntimeException("用户未登录"))
}
} else {
// 不需要登录的页面,直接放行
callback.onContinue(postcard)
}
}
companion object {
/** 需要登录的标记位,在 @Route 的 extras 参数中设置 */
const val NEED_LOGIN = 0x01
}
}
5.3 在路由目标上标记需要登录
// extras 参数用于携带自定义元数据,拦截器可以读取这些信息做决策
@Route(path = "/order/list", extras = 0x01) // 0x01 = NEED_LOGIN
class OrderListActivity : AppCompatActivity() {
// ...
}
// 不需要登录的页面不设置 extras
@Route(path = "/about/us")
class AboutActivity : AppCompatActivity() {
// ...
}
5.4 绿色通道:跳过所有拦截器
某些场景下(如跳转到登录页本身),不应被拦截器处理。ARouter 提供了"绿色通道"机制:
ARouter.getInstance()
.build("/user/login")
.greenChannel() // 跳过所有拦截器
.navigation()
设计要点:IProvider(服务类型)的路由默认走绿色通道,因为服务发现不涉及页面跳转,不需要拦截器处理。
5.5 拦截器的执行顺序
ARouter.build("/order/list").navigation()
│
▼
┌──────────────┐ 通过 ┌──────────────┐ 通过 ┌──────────────┐
│ Interceptor │ ──────→ │ Interceptor │ ──────→ │ Interceptor │ ──→ 执行跳转
│ priority = 1 │ │ priority = 5 │ │ priority = 8 │
│ (最先执行) │ │ │ │ (登录检查) │
└──────────────┘ └──────────────┘ └──────────────┘
│ 中断 │ 中断
▼ ▼
终止路由,回调 onInterrupt 终止路由,跳转登录页
拦截器按 priority 升序排列(值越小优先级越高),在子线程中依次执行。这意味着:
- 拦截器内部可以执行耗时操作(如网络请求校验 Token 有效性)
- 如果某个拦截器超时未调用
onContinue或onInterrupt,ARouter 会在默认超时(300 秒)后自动中断
六、 跨模块服务发现(IProvider)
6.1 解决的问题
组件化不仅需要解耦页面跳转,还需要解耦方法调用。例如,ModuleOrder(订单模块)需要调用 ModuleUser(用户模块)的 getUserInfo() 方法来获取当前登录用户信息,但两个模块之间不能有编译期依赖。
ARouter 的 IProvider 机制提供了一套完整的服务发现方案:将"接口"与"实现"分离到不同模块,通过 ARouter 在运行时动态查找并绑定。
6.2 架构设计
┌─────────────────────────────────────────────────────┐
│ 公共基础模块 (lib_base) │
│ │
│ public interface IUserService extends IProvider { │
│ UserInfo getCurrentUser(); │
│ boolean isLoggedIn(); │
│ } │
└──────────────────┬──────────────────────────────────┘
│ 接口依赖(仅此一条)
┌─────────┴─────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ ModuleUser │ │ ModuleOrder │
│ │ │ │
│ @Route(...) │ │ @Autowired │
│ UserService │ │ IUserService │
│ Impl │ │ userService │
│ │ │ │
│ 实现接口 │ │ 通过 ARouter │
│ │ │ 自动注入实现 │
└──────────────┘ └──────────────┘
6.3 实现步骤
第一步:在公共模块中定义接口
// lib_base 模块中
interface IUserService : IProvider {
/** 获取当前登录用户信息 */
fun getCurrentUser(): UserInfo?
/** 判断是否已登录 */
fun isLoggedIn(): Boolean
}
第二步:在用户模块中实现接口
// module_user 模块中
@Route(path = "/user/service")
class UserServiceImpl : IUserService {
private lateinit var context: Context
override fun init(context: Context) {
this.context = context
// 初始化逻辑,如加载缓存的用户信息
}
override fun getCurrentUser(): UserInfo? {
return UserRepository.getCachedUser()
}
override fun isLoggedIn(): Boolean {
return TokenManager.hasValidToken()
}
}
第三步:在订单模块中使用服务
// module_order 模块中——完全不依赖 module_user
@Route(path = "/order/create")
class CreateOrderActivity : AppCompatActivity() {
// 方式一:通过 @Autowired 按类型注入(推荐)
@Autowired
@JvmField
var userService: IUserService? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
// 使用服务
if (userService?.isLoggedIn() == true) {
val user = userService?.getCurrentUser()
Log.d("CreateOrder", "当前用户: ${user?.name}")
}
}
}
// 方式二:通过 navigation() 主动查找
fun getUserServiceDirectly(): IUserService? {
return ARouter.getInstance().navigation(IUserService::class.java)
}
6.4 按名称查找 vs 按类型查找
ARouter 提供了两种服务发现方式:
// 按类型查找(byType)——当接口只有一个实现时使用
val service = ARouter.getInstance().navigation(IUserService::class.java)
// 按名称查找(byName)——当接口有多个实现时,通过路径区分
val service = ARouter.getInstance()
.build("/user/service")
.navigation() as IUserService
设计考量:按类型查找更简洁,但如果同一个接口有多个实现(如灰度环境和正式环境使用不同的服务实现),就需要按名称查找来明确指定。
七、 降级策略(DegradeService)
7.1 为什么需要降级
在大型项目中,路由跳转可能因各种原因失败:目标页面被移除、路径拼写错误、动态路由未注册等。如果不处理,用户点击后将毫无反应,体验极差。
ARouter 提供了两层降级保护:
7.2 局部降级:NavigationCallback
针对单次跳转,通过回调监听跳转结果:
ARouter.getInstance()
.build("/order/detail")
.navigation(this, object : NavigationCallback {
override fun onFound(postcard: Postcard) {
// 找到了目标路由
}
override fun onLost(postcard: Postcard) {
// 未找到目标路由——显示错误页或 Toast
Toast.makeText(this@MainActivity, "页面走丢了", Toast.LENGTH_SHORT).show()
}
override fun onArrival(postcard: Postcard) {
// 跳转完成
}
override fun onInterrupt(postcard: Postcard) {
// 被拦截器中断
}
})
7.3 全局降级:DegradeService
定义一个全局兜底处理器,当任何路由未找到目标时自动触发:
/**
* 全局路由降级处理
* 当路由找不到目标时,统一跳转到 404 页面
*/
@Route(path = "/service/degrade")
class GlobalDegradeService : DegradeService {
override fun init(context: Context) {}
override fun onLost(context: Context, postcard: Postcard) {
// 统一跳转到错误页面,并携带原始路径信息用于排查
ARouter.getInstance()
.build("/common/error")
.withString("lost_path", postcard.path)
.greenChannel()
.navigation(context)
}
}
八、 高级用法
8.1 URL 跳转与 Deep Link
ARouter 天然支持 URL 协议跳转,这使得它可以作为 App 的 Deep Link 入口:
/**
* Scheme 过滤器——接收来自浏览器或其他 App 的 URL 请求
* 在 AndroidManifest 中注册 intent-filter 即可
*/
class SchemeFilterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 将系统传入的 URI 直接交给 ARouter 处理
val uri = intent.data
if (uri != null) {
ARouter.getInstance()
.build(uri)
.navigation(this)
}
finish()
}
}
<!-- AndroidManifest.xml -->
<activity android:name=".SchemeFilterActivity">
<intent-filter>
<data android:scheme="myapp" android:host="m.example.com"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
此后,浏览器中打开 myapp://m.example.com/order/detail?orderId=123 就能直接跳转到原生的订单详情页。
8.2 动态路由注册
对于插件化架构或需要运行时动态添加路由的场景,ARouter 支持手动注册路由信息:
// 动态注册一个路由——目标页不需要 @Route 注解
ARouter.getInstance().addRouteGroup { atlas ->
atlas["/dynamic/page"] = RouteMeta.build(
RouteType.ACTIVITY, // 路由类型
DynamicPageActivity::class.java, // 目标类
"/dynamic/page", // 路径
"dynamic", // 分组
0, // 优先级
0 // extras
)
}
8.3 预处理服务(PretreatmentService)
在路由跳转的最早期(拦截器之前),你可以通过 PretreatmentService 做全局预处理:
@Route(path = "/service/pretreatment")
class PretreatmentServiceImpl : PretreatmentService {
override fun init(context: Context) {}
/**
* 路由预处理
* @return true 表示继续路由流程;false 表示由自己处理,终止 ARouter 的后续流程
*/
override fun onPretreatment(context: Context, postcard: Postcard): Boolean {
// 示例:将所有内部路由的埋点统一在此处处理
AnalyticsTracker.trackPageRoute(postcard.path, postcard.extras)
return true // 继续正常路由
}
}
九、 实战案例:电商 App 组件化路由
下面用一个贴近真实项目的案例,展示 ARouter 如何串联多模块协作:
┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ app (壳工程) │ │ module_home │ │ module_order │ │ module_user │
│ │ │ │ │ │ │ │
│ Application │ │ HomeFragment│ │ OrderList │ │ LoginActivity│
│ ARouter.init │ │ │ │ OrderDetail │ │ UserService │
│ │ │ │ │ CreateOrder │ │ UserProfile │
└──────┬───────┘ └──────┬──────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
└───────────────────┴──────────────────┴───────────────────┘
│
┌───────┴───────┐
│ lib_base │
│ │
│ IUserService │
│ IOrderService │
│ 路由路径常量 │
└───────────────┘
路由路径常量集的管理(推荐放在公共模块中):
// lib_base 模块中
object RouterPaths {
// 首页模块
const val HOME_MAIN = "/home/main"
// 订单模块
const val ORDER_LIST = "/order/list"
const val ORDER_DETAIL = "/order/detail"
const val ORDER_CREATE = "/order/create"
// 用户模块
const val USER_LOGIN = "/user/login"
const val USER_PROFILE = "/user/profile"
const val USER_SERVICE = "/user/service"
}
典型调用链:
// HomeFragment 中点击"我的订单"
binding.btnMyOrders.setOnClickListener {
ARouter.getInstance()
.build(RouterPaths.ORDER_LIST)
.navigation()
// 路由流程:
// 1. ARouter 在路由表中查找 "/order/list" → 找到 OrderListActivity
// 2. 检查到该路由 extras = NEED_LOGIN → 触发 LoginInterceptor
// 3. LoginInterceptor 检查登录态
// → 已登录:放行 → 跳转到 OrderListActivity
// → 未登录:拦截 → 跳转到 LoginActivity(携带 redirect 路径)
// 4. 用户登录成功后 → 从 redirect 路径恢复跳转到 OrderListActivity
}
十、 ARouter 的适用边界与现代替代方案
10.1 什么时候适合用 ARouter
- 多团队协作的组件化大型项目:模块间需要编译期隔离
- Deep Link / URL Scheme 驱动的跳转:运营推送、H5 跳原生
- 需要全局拦截器的统一跳转控制:登录检查、AB 实验、权限校验
- 跨模块服务发现:模块间的方法级通信
10.2 什么时候不适合用 ARouter
- 单模块小型项目:直接 Intent 跳转即可,引入路由框架是过度设计
- 模块内部的页面跳转:同模块内 Intent 或 Navigation Component 更简洁
- 需要强类型安全的导航:ARouter 的路径是字符串,缺乏编译期校验
10.3 与 Jetpack Navigation 的对比
| 维度 | ARouter | Jetpack Navigation |
|---|---|---|
| 核心定位 | 组件化路由,解耦多模块 | 单 App 内导航,管理页面栈 |
| 路由方式 | 字符串路径 | XML 导航图 + Safe Args(强类型) |
| 编译期安全 | ❌ 路径字符串无校验 | ✅ Safe Args 编译期生成参数类 |
| 拦截器 | ✅ 全局拦截器链 | ⚠️ 需要手动实现 NavController 监听 |
| 跨模块支持 | ✅ 核心能力 | ⚠️ 需要 Dynamic Feature Module 配合 |
| 服务发现 | ✅ IProvider | ❌ 不支持 |
| Deep Link | ✅ 原生支持 | ✅ 内置支持 |
| Fragment 支持 | ✅ 通过 navigation() 获取 | ✅ 原生支持 |
结论:ARouter 和 Jetpack Navigation 的定位不同,不存在"谁替代谁"。在组件化场景下,ARouter 解决的是跨模块的路由和服务发现问题;Jetpack Navigation 解决的是模块内部的页面导航和栈管理问题。在大型项目中,二者往往是共存的。
十一、 总结
ARouter 的核心价值可以归纳为三个层面:
- 路由跳转:通过字符串路径取代类引用,实现页面跳转的编译期解耦
- 拦截器机制:在路由过程中插入 AOP 切面,统一处理登录检查、权限校验等横切逻辑
- 服务发现:通过
IProvider+@Autowired实现跨模块的方法级通信,彻底隔离模块依赖
在实际使用中,ARouter 的 API 设计遵循了"简单场景简单用、复杂场景灵活扩展"的原则。三行代码就能完成一次基础跳转,但在你需要时,拦截器、降级服务、动态注册、预处理服务等高级能力随时待命。
在下一篇文章中,我们将深入 ARouter 的源码,剖析 @Route 注解处理器如何在编译期生成路由映射表、Warehouse 如何管理内存中的路由信息、LogisticsCenter 如何完成路由补全、以及 arouter-register 插件如何通过 ASM 字节码插桩优化初始化性能。