Q1:组件化架构中,ARouter 如何实现模块间页面跳转和服务调用?请简述原理并画出路由表加载流程图,给出关键源码。
答案要点
- 核心原理 :ARouter 通过 注解 + APT 在编译期为每个模块生成路由表类(如
RouteTable$$Group$$xxx),运行时利用 类加载 和 分组加载 策略按需初始化路由表,通过RouteActivity(或RouteService)代理完成跳转。 - 页面跳转 :调用
ARouter.getInstance().build(path).navigation()→ 查找路由元数据 → 如果不是Activity类型则用Intent启动,否则通过ActivityCompat.startActivity。 - 服务调用 :通过
@Autowired注解自动注入服务,或ARouter.getInstance().navigation(Service.class)获取实例,底层使用 接口 + 实现类映射,本质是依赖倒置。
流程图(路由表加载)
flowchart TD
A[业务调用 navigation(path)] --> B[Postcard 构建]
B --> C[查 Warehouse.routes\n 是否已加载]
C -- 命中 --> D[返回 RouteMeta]
C -- 未命中 --> E[按 group 加载路由表\n LogisticsCenter.completion]
E --> F[通过类名加载 group 路由表类\n RouteTable$$Group$$xxx]
F --> G[执行 loadInto 方法填充 Warehouse]
G --> D
D --> H{目标类型}
H -- Activity --> I[创建 Intent 并 startActivity]
H -- Fragment --> J[通过反射实例化]
H -- Service --> K[返回服务代理对象]
I & J & K --> L[完成导航]
精简源码
java
// 1. 注解定义路由路径
@Route(path = "/order/OrderActivity")
public class OrderActivity extends AppCompatActivity {}
// 2. APT 生成的路由表片段(简化)
public class ARouter$$Group$$order implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/order/OrderActivity",
RouteMeta.build(RouteType.ACTIVITY, OrderActivity.class, "/order/OrderActivity", "order"));
}
}
// 3. 运行时加载逻辑(LogisticsCenter 核心方法)
public synchronized static void completion(Postcard postcard) {
Map<String, RouteMeta> routeMetaMap = Warehouse.routes;
if (routeMetaMap.containsKey(postcard.getPath())) {
postcard.setDestination(routeMetaMap.get(postcard.getPath()).getDestination());
return;
}
String className = "com.alibaba.android.arouter.routes.ARouter$$Group$$" + postcard.getGroup();
Class<?> groupClass = Class.forName(className);
IRouteGroup group = (IRouteGroup) groupClass.getConstructor().newInstance();
Map<String, RouteMeta> newMap = new HashMap<>();
group.loadInto(newMap);
Warehouse.routes.putAll(newMap);
postcard.setDestination(newMap.get(postcard.getPath()).getDestination());
}
Q2:组件化与模块化的核心区别是什么?为什么大型项目必须从模块化演进到组件化?
答案要点
- 模块化 :按功能拆分代码,但所有模块最终打包到一个 APK ,模块间可直接依赖(
implementation project),无法独立运行或调试。 - 组件化 :每个业务组件可独立作为 App 运行 ,集成时作为 Library;组件间零直接依赖,仅通过路由(如 ARouter)或服务接口通信。
- 演进必要性 :
- 编译速度:模块化修改任意模块都会触发整个项目重编;组件化可单独编译修改的组件。
- 团队协作:模块化容易产生代码耦合和合并冲突;组件化强制隔离,每个团队维护独立组件。
- 动态交付:组件化天然支持按需下载(Play Feature Delivery)或插件化。
架构流程图
flowchart LR
subgraph 模块化
A1[common] -->|直接依赖| A2[user]
A1 -->|直接依赖| A3[order]
A2 --> A3
end
subgraph 组件化
B1[common 基础库] -.->|路由| B2[user 组件]
B1 -.->|路由| B3[order 组件]
B2 -.-x|禁止直接依赖| B3
end
精简源码(独立运行开关)
groovy
// gradle.properties
isUserComponentAlone=true
// user 模块 build.gradle
if (isUserComponentAlone.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
defaultConfig {
if (isUserComponentAlone.toBoolean()) {
applicationId "com.user.component"
}
}
sourceSets {
main {
if (isUserComponentAlone.toBoolean()) {
manifest.srcFile 'src/main/alone/AndroidManifest.xml'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
Q3:ARouter 的核心原理(APT + 运行时加载)?画出路由表生成与加载流程图。
答案要点
- 编译时 :APT 扫描
@Route注解 → 生成ARouter$$Root$$模块名、ARouter$$Group$$分组名和ARouter$$Providers$$模块名等类。 - 运行时初始化 :
ARouter.init()→ 通过LogisticsCenter加载路由表(Dex 扫描或插件注入)→ 存入Warehouse静态缓存。 - 路由分发 :
navigation()→ 根据 path 查找RouteMeta→ 反射创建目标(Activity 走 Intent,Fragment 直接实例化,Service 返回代理)。
流程图
flowchart TD
subgraph 编译期
A[Java 源码 @Route] --> B[APT 处理]
B --> C[生成 IRouteGroup / IRouteRoot 类]
end
subgraph 运行时初始化
D[ARouter.init] --> E[LogisticsCenter 加载路由表]
E --> F[扫描 Dex / 插件注册]
F --> G[填充 Warehouse.routes\n Warehouse.providers]
end
subgraph 路由跳转
H[build + navigation] --> I[匹配 Postcard]
I --> J{检查目标类型}
J -->|Activity| K[Intent 启动]
J -->|Fragment| L[反射 newInstance]
J -->|IProvider| M[返回实例]
end
精简源码
java
// 编译期生成的 Root 类示例
public class ARouter$$Root$$order implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("order", ARouter$$Group$$order.class);
}
}
// 运行时 LogisticsCenter 关键代码
private static void loadRouterMap() {
for (String className : getRouterRootClasses()) {
IRouteRoot root = (IRouteRoot) Class.forName(className).newInstance();
root.loadInto(Warehouse.groupsIndex);
}
}
Q4:ARouter 相比原生隐式/显式 Intent 跳转有哪些核心优势?为什么大型项目不用原生路由?
答案要点
- 解耦:原生显式需要直接依赖目标 Activity 类;隐式需在 Manifest 配置大量 intent-filter,难以维护。ARouter 只需路径字符串。
- 编译期检查:ARouter 在编译期检查路径是否被注册,写错路径直接报错;原生隐式 action 写错运行时才暴露。
- 参数自动注入 :ARouter 的
@Autowired可自动从 Intent/Bundle 取值赋值;原生需要手动getXxxExtra。 - 拦截器/降级:ARouter 支持全局登录、权限拦截和统一 404 处理;原生需在每个页面编写重复代码。
- 跨模块服务调用:ARouter 的 IProvider 支持无依赖调用方法;原生不具备。
对比流程图
flowchart LR
subgraph 原生显式
A[写死目标类] --> B[编译通过]
B --> C[运行时类缺失?] -->|是| D[崩溃]
end
subgraph ARouter
E[注解路径] --> F[APT生成路由表]
F --> G[编译期检查路径存在?] -->|否| H[编译报错]
G -->|是| I[运行时安全跳转]
end
精简源码
java
// 原生显式(强依赖)
Intent intent = new Intent(this, OrderActivity.class);
intent.putExtra("orderId", "123");
startActivity(intent);
// ARouter(解耦 + 编译检查 + 自动注入)
@Route(path = "/order/OrderActivity")
public class OrderActivity extends AppCompatActivity {
@Autowired String orderId;
@Override protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
ARouter.getInstance().inject(this);
}
}
// 调用
ARouter.getInstance().build("/order/OrderActivity").withString("orderId", "123").navigation();
Q5:ARouter 的拦截器机制如何工作?如何实现全局登录拦截?给出流程图和代码。
答案要点
- 工作原理 :ARouter 通过
IInterceptor接口和@Interceptor注解定义拦截器,编译期收集并按优先级排序。navigation()执行时会依次执行所有拦截器的process方法,支持同步或异步放行/中断跳转。 - 登录拦截实现 :自定义拦截器读取用户登录状态,若未登录则弹窗或跳转登录页,并通过
callback.onInterrupt()中断原目标页面的跳转。
流程图
flowchart TD
A[navigation 请求] --> B[创建 Postcard]
B --> C[获取 Interceptor 链]
C --> D[按优先级排序拦截器列表]
D --> E[for 每个拦截器\n调用 process]
E --> F{拦截器调用 onContinue?}
F -- 是 --> G[继续下一个拦截器]
G --> H[所有拦截器通过]
H --> I[执行真实跳转]
F -- 否(onInterrupt) --> J[抛出中断异常\n跳转失败]
精简源码
java
@Interceptor(priority = 8, name = "login")
public class LoginInterceptor implements IInterceptor {
private Context context;
@Override
public void init(Context ctx) { this.context = ctx; }
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
boolean needLogin = !postcard.getPath().startsWith("/login");
if (needLogin && !UserManager.isLogin()) {
ARouter.getInstance().build("/login/LoginActivity")
.navigation(context, new NavigationCallback() {
@Override public void onArrival(Postcard postcard) {
ARouter.getInstance().build(postcard.getPath()).navigation();
}
@Override public void onInterrupt(Postcard postcard) {}
});
callback.onInterrupt(new Exception("need login"));
return;
}
callback.onContinue(postcard);
}
}
Q6:组件化中如何实现模块独立调试(Gradle + Manifest 切换)?给出配置方案。
答案要点
- 通过 Gradle 动态切换插件 :在模块的
build.gradle中,通过一个布尔变量isRunAlone控制apply plugin: 'com.android.application'还是'com.android.library'。 - AndroidManifest 切换 :为独立运行单独放一份带
<intent-filter>的 Manifest 文件,放在src/main/alone/下;在sourceSets中根据isRunAlone选择不同的 Manifest。 - Application 区分 :独立运行时需要自己的 Application,可以通过
buildConfigField生成常量,或者用src/main/alone/java下单独的应用入口。
流程图
flowchart TD
A[gradle.properties\nisRunAlone=true/false] --> B{isRunAlone?}
B -->|true| C[apply plugin: 'com.android.application']
B -->|false| D[apply plugin: 'com.android.library']
C --> E[使用 standalone AndroidManifest\n含启动Activity]
D --> F[使用 library AndroidManifest\n无intent-filter]
E --> G[组件可独立运行]
F --> H[组件作为依赖被宿主集成]
精简源码
groovy
// gradle.properties
isRunAlone = true
// 模块 build.gradle
if (isRunAlone.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
android {
defaultConfig {
if (isRunAlone.toBoolean()) {
applicationId "com.user.component"
}
}
sourceSets {
main {
if (isRunAlone.toBoolean()) {
manifest.srcFile 'src/main/alone/AndroidManifest.xml'
java.srcDirs += 'src/main/alone/java'
} else {
manifest.srcFile 'src/main/AndroidManifest.xml'
}
}
}
}
Q7:ARouter 的依赖注入(自动装配)原理是什么?如何跨模块注入服务?
答案要点
- 原理 :通过
@Autowired注解标记字段,APT 生成辅助类(目标类$$ARouter$$Autowired),在inject方法中通过ARouter.getInstance().build(path).navigation()获取服务实例,并反射/直接赋值给字段。 - 跨模块注入 :暴露服务的模块使用
@Route注解标记服务实现类(type = RouteType.PROVIDER),调用方模块仅依赖服务接口(公共 API 模块),通过@Autowired声明接口字段即可获取服务实例。
精简源码
java
// common-api 模块(接口)
public interface IOrderService {
void submitOrder(String id);
}
// order 模块(实现)
@Route(path = "/order/service")
public class OrderServiceImpl implements IOrderService, IProvider {
@Override public void submitOrder(String id) { /* 实现 */ }
@Override public void init(Context context) { }
}
// 调用方模块(依赖注入)
public class MainActivity extends AppCompatActivity {
@Autowired(name = "/order/service")
IOrderService orderService;
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
ARouter.getInstance().inject(this);
orderService.submitOrder("123");
}
}
Q8:如何解决路由路径重复、编译期检查以及模块化路由表的自动生成?(APT + Gradle 插件)
答案要点
- 路径重复检查 :APT 在生成路由表时校验同一个 group 内是否存在 相同 path ,若有则报编译错误(
@Route重复注解)。 - 编译期检查 :通过自定义
AbstractProcessor扫描所有@Route节点,验证path格式必须以/开头且至少有两段。 - 路由表自动生成 :APT 为每个
module生成ARouter$$Root$$模块名和ARouter$$Group$$分组名两个类。
流程图
flowchart TD
A[编译期扫描@Route] --> B[提取path/group]
B --> C{同一group内\npath是否重复?}
C -->|是| D[编译报错: Duplicate route]
C -->|否| E[生成 ARouter$$Group$$xxx]
E --> F[生成 ARouter$$Root$$xxx]
F --> G[打包进 APK]
精简源码(APT 核心逻辑)
java
@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 env) {
Map<String, List<RouteMeta>> groupMap = new HashMap<>();
for (Element element : env.getElementsAnnotatedWith(Route.class)) {
Route route = element.getAnnotation(Route.class);
String path = route.path();
if (!path.startsWith("/") || path.length() < 2 || path.split("/").length < 3) {
error(element, "Path must start with '/' and contain at least two segments.");
}
String key = route.group() + "_" + route.path();
if (processedSet.contains(key)) {
error(element, "Duplicate route path: " + path);
}
processedSet.add(key);
String groupName = TextUtils.isEmpty(route.group()) ? path.split("/")[1] : route.group();
RouteMeta meta = new RouteMeta(route.type(), element, route.path(), groupName);
groupMap.computeIfAbsent(groupName, k -> new ArrayList<>()).add(meta);
}
generateGroupFiles(groupMap);
return true;
}
}
Q9:ARouter 的"自动注册"原理?有无 Gradle 插件方案?对比传统 Dex 扫描方式优劣。
答案要点
- ARouter 早期版本 :通过扫描 Dex 中所有类,查找
com.alibaba.android.arouter.routes.前缀的类,使用DexFile或Class.forName加载,但效率低。 - 当前版本推荐方式 :使用 ARouter 的 Gradle 插件
arouter-register,在字节码插入阶段把生成的路由表类全部收集到一个集合中,避免运行时全量扫描。 - 优劣对比 :
- 插件方式:编译期插入,运行时只加载已知类,性能高,但增加编译流程复杂度。
- 扫描方式:无需插件,但 Android 9+ 对
DexFile限制增加,耗时较大。
流程图
flowchart TD
subgraph Dex扫描方式
A1[ARouter.init] --> A2[遍历dex中所有类]
A2 --> A3[查找 routes. 前缀类]
A3 --> A4[反射加载并loadInto]
A4 --> A5[耗时50-200ms]
end
subgraph Gradle插件方式
B1[编译期Transform扫描] --> B2[收集所有IRouteRoot类]
B2 --> B3[生成注册类,硬编码loadInto]
B3 --> B4[ARouter.init直接调用注册]
B4 --> B5[耗时近0ms]
end
精简源码(Dex 扫描方式)
java
private static void loadRouterMapFromDex() {
List<String> classNames = DexFileHelper.getAllClassesFromDex(context);
for (String className : classNames) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE)) {
IRouteRoot root = (IRouteRoot) Class.forName(className).newInstance();
root.loadInto(Warehouse.groupsIndex);
}
}
}
Q10:组件化中,ARouter 如何处理模块未安装时的降级策略?如何实现自定义降级服务?
答案要点
- 降级场景 :目标
Activity所在的模块未集成(APK 中无该类),或路由找不到对应的RouteMeta。 - 默认行为 :调用
callback.onLost()并可通过NavigationCallback获知失败;若回调未设置,则打印日志不做任何事。 - 自定义降级 :实现
DegradeService接口,并在@Route中声明路径(如/arouter/service/degrade),ARouter 在路由失效时会回调该服务的onLost方法。
精简源码
java
@Route(path = "/arouter/service/degrade")
public class CustomDegradeService implements DegradeService {
private Context context;
@Override
public void onLost(Context context, Postcard postcard) {
Toast.makeText(context, "模块未安装或页面不存在", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(context, ErrorActivity.class);
intent.putExtra("error_path", postcard.getPath());
context.startActivity(intent);
}
@Override
public void init(Context context) { this.context = context; }
}
// 使用(自动生效)
ARouter.getInstance().build("/home/not_exist").navigation();
Q11:ARouter 中 IProvider 和普通接口的区别?为什么服务接口必须继承 IProvider?
答案要点
- 核心区别 :
- 普通接口:仅定义方法,无初始化、生命周期管理。
- IProvider:ARouter 服务接口,自带
init(Context context)方法,支持初始化、全局单例管理。
- 必须继承 IProvider:ARouter 通过该接口识别服务类,统一管理实例生命周期(初始化→复用→销毁),确保服务全局唯一。
精简源码
java
public interface IProvider {
void init(Context context);
}
// 正确示例
@Route(path = "/service/common")
public class CommonService implements IProvider {
@Override public void init(Context ctx) { }
public void doSomething() { }
}
Q12:组件化中如何基于 ARouter 服务实现模块间事件通知(无需 EventBus)?
答案要点
- 思路:定义一个事件分发服务,各观察者模块实现该服务并注册到 ARouter;事件发布者通过服务接口调用所有观察者。
- 优势:类型安全,无需反射;劣势:需要手动管理注册/反注册。
流程图
flowchart TD
A[公共模块定义 IEventService] --> B[观察者模块实现 IObserver\n并在init中注册到 IEventService]
C[事件发布模块] --> D[通过ARouter获取 IEventService]
D --> E[调用 dispatchEvent]
E --> F[IEventService 遍历观察者列表]
F --> G[回调观察者的 onEvent 方法]
精简源码
java
// 公共接口
public interface IEventService extends IProvider {
void registerObserver(String eventType, IObserver observer);
void dispatchEvent(String eventType, Object data);
}
public interface IObserver extends IProvider {
void onEvent(String eventType, Object data);
}
// 观察者(订单模块)
@Route(path = "/observer/order")
public class OrderObserver implements IObserver {
@Override public void onEvent(String eventType, Object data) {
if ("login_success".equals(eventType)) refreshOrderList();
}
@Override public void init(Context ctx) {
IEventService es = ARouter.getInstance().navigation(IEventService.class);
es.registerObserver("login_success", this);
}
}
Q13:@Autowired 注入自定义类型参数的底层原理?如何实现 SerializationService?
答案要点
- 原理 :APT 在生成的注入类中,对于非基础类型(如
UserInfo),会调用SerializationService的parseObject方法将 Intent 中传递的字符串反序列化为对象。 - 实现自定义 SerializationService :实现接口,使用 Gson,并用
@Route标注。
精简源码
java
// 自定义类型
public class UserInfo implements Serializable {
public String name;
public int age;
}
// 目标页面
public class UserActivity extends AppCompatActivity {
@Autowired UserInfo userInfo;
@Override protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
ARouter.getInstance().inject(this);
}
}
// 自定义序列化服务
@Route(path = "/arouter/service/gson")
public class GsonSerializationService implements SerializationService {
private Gson gson = new Gson();
@Override public <T> T json2Object(String text, Class<T> clazz) { return gson.fromJson(text, clazz); }
@Override public String object2Json(Object instance) { return gson.toJson(instance); }
@Override public void init(Context context) { }
}
// 调用
ARouter.getInstance().build("/user/UserActivity")
.withObject("userInfo", new UserInfo("张三", 18))
.navigation();
Q14:如何让 ARouter 支持 URL 参数自动注入(结合 @Autowired)?
答案要点
- ARouter 本身的 URL 跳转 :
ARouter.getInstance().build("order://order?id=123")会解析 scheme,但需要配合@Autowired注入参数。 - 原理 :
build(url)内部会解析 query 参数并存入Postcard的 bundle 中,目标页面调用inject(this)后,APT 生成的代码会从 Intent extras 中取值赋值给带@Autowired的字段。
精简源码
java
@Route(path = "/order/OrderActivity")
public class OrderActivity extends AppCompatActivity {
@Autowired String orderId;
@Autowired int type;
@Override protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
ARouter.getInstance().inject(this);
}
}
// 通过 URL 启动
ARouter.getInstance().build("order://order/OrderActivity?orderId=abc123&type=2").navigation();
Q15:ARouter 如何处理 Activity 和 Fragment 的跳转差异?Fragment 跳转注意事项?
答案要点
- 跳转差异 :
- Activity:通过
context.startActivity跳转。 - Fragment:
navigation()返回Object,需强转为Fragment,然后调用方通过FragmentTransaction添加。
- Activity:通过
- 注意事项 :Fragment 必须有无参构造;需传入宿主 Activity 的
FragmentManager;不要缓存实例。
流程图
flowchart TD
A[ARouter.build(fragment_path).navigation] --> B[返回 Object (Fragment实例)]
B --> C[调用方强转为 Fragment]
C --> D[通过 FragmentTransaction\n执行 replace/add]
D --> E[需传入宿主 FragmentManager]
精简源码
java
@Route(path = "/order/OrderFragment")
public class OrderFragment extends Fragment {
@Autowired String orderId;
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
}
}
// 宿主 Activity 中使用
Fragment fragment = (Fragment) ARouter.getInstance()
.build("/order/OrderFragment")
.withString("orderId", "10086")
.navigation();
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, fragment)
.commit();
Q16:ARouter 的 GreenChannel 是什么?使用场景和副作用?
答案要点
- GreenChannel :ARouter 提供的快速通道,开启后跳转动作会 跳过拦截器和降级服务,直接执行页面跳转。
- 使用场景:用于必须成功的高优先级跳转(如崩溃恢复页),或避免因拦截器逻辑卡死导致无法跳转。
- 副作用:将失效全局登录拦截、埋点拦截等业务逻辑,需谨慎使用。
流程图
flowchart LR
A[navigation请求] --> B{是否开启greenChannel?}
B -->|是| C[跳过所有拦截器\n直接执行跳转]
B -->|否| D[执行完整拦截器链]
D --> E{拦截器是否中断?}
E -->|否| C
E -->|是| F[跳转被阻止]
精简源码
java
ARouter.getInstance()
.build("/order/OrderActivity")
.greenChannel()
.navigation();
Q17:ARouter 支持 startActivityForResult 吗?如何实现?与原生相比有何限制?
答案要点
- 支持 :通过
navigation(Activity, int)或navigation(context, callback, requestCode)实现。 - 实现 :ARouter 内部创建一个代理 Activity,启动目标,代理 Activity 接收
onActivityResult并回调给调用方。 - 限制 :
requestCode可能被转换;某些launchMode下onActivityResult可能提前回调;无法传递ActivityOptions。
流程图
flowchart TD
A[调用 navigation(Activity, requestCode)] --> B[ARouter创建代理Activity]
B --> C[代理Activity启动目标Activity]
C --> D[目标Activity finish]
D --> E[代理Activity收到 onActivityResult]
E --> F[结果回调给原调用方的 onActivityResult]
精简源码
java
ARouter.getInstance()
.build("/order/OrderActivity")
.navigation(this, 1001);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1001) {
// 处理返回数据
}
}
Q18:ARouter 如何处理 Activity/Fragment 的 extras 字段和自定义转场动画?
答案要点
withFlags:通过Postcard.withFlags(int flags)添加 Intent 标志。- 转场动画 :通过
withTransition(int enterAnim, int exitAnim)设置。 - 自定义额外参数 :
withBundle等可传递任意数据。
流程图
flowchart LR
A[build(path)] --> B[withXXX 添加参数]
B --> C[withFlags / withTransition]
C --> D[navigation 生成 Intent]
D --> E[Intent 携带 extras + ActivityOptions]
E --> F[startActivity]
精简源码
java
ARouter.getInstance()
.build("/order/OrderActivity")
.withFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.withTransition(R.anim.slide_in_right, R.anim.slide_out_left)
.withString("orderId", "123")
.navigation();
Q19:如何解决组件化中的资源名冲突?ARouter 能否避免?资源前缀配置。
答案要点
- 资源冲突原因:不同模块可能定义相同名称的资源,构建时合并到主项目出现覆盖。
- ARouter 不解决资源冲突,只处理路由字符串。
- 解决方案 :资源前缀
resourcePrefix "order_",强制模块内资源以该前缀开头。
流程图
flowchart TD
A[模块A: layout/activity_main.xml] --> C[编译合并]
B[模块B: layout/activity_main.xml] --> C
C --> D[后者覆盖前者,产生冲突]
E[解决方案: resourcePrefix "module_"] --> F[强制资源名前缀]
F --> G[避免同名覆盖]
精简源码
groovy
android {
resourcePrefix "order_"
defaultConfig {
resConfigs "zh-rCN", "en"
}
}
Q20:组件化中多个模块依赖第三方库不同版本时如何解决?ARouter 是否涉及?
答案要点
- 冲突原因 :Gradle 默认使用最高版本,可能导致 API 不兼容引发
NoSuchMethodError。 - 解决方案 :
resolutionStrategy.force强制统一版本,或使用 BOM。 - ARouter 不涉及:ARouter 自身依赖极少。
流程图
flowchart TD
A[模块A依赖 OkHttp 3.12] --> C[Gradle 版本仲裁]
B[模块B依赖 OkHttp 4.9] --> C
C --> D[默认选择最高版本 4.9]
D --> E{兼容性?}
E -->|否| F[NoSuchMethodError]
E -->|是| G[正常运行]
H[解决方案: resolutionStrategy.force] --> I[强制统一版本]
精简源码
groovy
subprojects {
configurations.all {
resolutionStrategy {
force 'com.squareup.okhttp3:okhttp:4.9.3'
}
}
}
Q21:组件化中如何统一管理各组件的生命周期(初始化/销毁)?
答案要点
- 核心方案 :组件生命周期注册 + 宿主分发。基础库定义
IComponentLifecycle接口,业务组件实现并通过 ARouter 注册到宿主,宿主生命周期回调时分发给各组件。
流程图
flowchart TD
A[基础库定义 IComponentLifecycle] --> B[组件A 实现接口\n@Route("/lifecycle/compA")]
A --> C[组件B 实现接口\n@Route("/lifecycle/compB")]
B & C --> D[宿主 Application.onCreate]
D --> E[通过 ARouter 获取所有生命周期实例]
E --> F[遍历实例调用 onCreate]
精简源码
java
public interface IComponentLifecycle extends IProvider {
void onCreate(Context context);
void onDestroy(Context context);
}
@Route(path = "/lifecycle/order")
public class OrderLifecycle implements IComponentLifecycle {
@Override public void onCreate(Context context) { OrderModule.init(context); }
@Override public void onDestroy(Context context) { OrderModule.release(); }
@Override public void init(Context context) { }
}
// 宿主
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
List<IComponentLifecycle> lifecycles = getAllLifecycles();
for (IComponentLifecycle lc : lifecycles) lc.onCreate(this);
}
}
Q22:基础库与业务组件的依赖边界如何划分?如何防止基础库臃肿?
答案要点
- 依赖边界:基础库只包含通用工具、网络、图片、路由等,无业务逻辑;业务组件依赖基础库,不依赖其他业务组件。
- 防止臃肿 :基础库按功能拆分(
base-core、base-network、base-router),业务组件按需依赖。
架构流程图
flowchart TD
subgraph 基础库集群
core[base-core]
net[base-network]
img[base-image]
router[base-router]
end
subgraph 业务组件
order[订单组件] --> net & img & router
user[用户组件] --> core & net & router
end
Q23:如何通过 Gradle Transform 插件实现 ARouter 路由表零反射、零 Dex 扫描的自动注册?
答案要点
- 问题:默认 Dex 扫描耗时。
- 优化原理 :利用 Transform API 在编译期扫描所有
IRouteRoot实现类,动态生成注册类,直接硬编码loadInto调用。 - 效果:运行时无需反射和扫描,耗时接近 0。
流程图
flowchart TD
A[编译期 .class 文件] --> B[Transform 遍历]
B --> C{是否属于 IRouteRoot 等} -- 是 --> D[收集类信息]
D --> E[生成 ARouter$$Register 类]
E --> F[该类包含所有 loadInto 硬编码调用]
F --> G[ARouter.init 改为调用 Register.register]
精简源码(Transform 核心)
java
public class RouterRegisterTransform extends Transform {
@Override
public void transform(TransformInvocation invocation) {
Set<String> routeRoots = new HashSet<>();
for (JarInput jar : invocation.getInputs()) {
if (className.startsWith(ROOT_PACKAGE) && className.endsWith(ROOT_SUFFIX)) {
routeRoots.add(className);
}
}
String code = generateRegisterCode(routeRoots);
// 写入 ARouterRegister.java
}
}
Q24:大型项目中如何监控 ARouter 路由性能并做非侵入式埋点?
答案要点
- 监控点 :路由表加载耗时、
navigation到onCreate时间、拦截器链执行总时长。 - 非侵入方案 :自定义
NavigationCallback记录时间戳;或使用 AOP 插桩。
流程图
flowchart TD
A[自定义 NavigationCallback] --> B[记录开始时间戳]
B --> C[onFound: 记录路由查找耗时]
C --> D[拦截器执行]
D --> E[onArrival: 记录完整跳转耗时]
E --> F[上报监控平台]
精简源码
java
public class PerformanceNavigationCallback implements NavigationCallback {
private long startTime;
public PerformanceNavigationCallback(String path) {
startTime = SystemClock.uptimeMillis();
}
@Override public void onFound(Postcard postcard) {
Log.d("Perf", "route found cost: " + (SystemClock.uptimeMillis() - startTime));
}
@Override public void onArrival(Postcard postcard) {
long cost = SystemClock.uptimeMillis() - startTime;
ReportHelper.reportRoutePerformance(postcard.getPath(), cost);
}
}
ARouter.getInstance().build("/xxx").navigation(context, new PerformanceNavigationCallback("/xxx"));
Q25:ARouter 的调试工具/日志如何使用?线上环境注意事项?
答案要点
- 调试配置 :
ARouter.openLog()打印日志;ARouter.openDebug()开启调试模式(支持热修复路由表)。 - 线上环境 :必须关闭
openDebug(),建议关闭openLog()。
流程图
flowchart LR
A[ARouter.openLog()] --> B[打印路由查找、加载、注入日志]
C[ARouter.openDebug()] --> D[支持热修复路由表\n跳过部分校验]
E[线上环境] --> F[关闭 openLog 和 openDebug]
精简源码
java
if (BuildConfig.DEBUG) {
ARouter.openLog();
ARouter.openDebug();
}
ARouter.init(context);
Q26:如何利用 ARouter + Play Core 实现组件的按需下载(动态功能模块)?
答案要点
- 核心流程 :将组件配置为
dynamic-feature,跳转时捕获onLost,触发SplitInstallManager下载,安装后重新加载路由表并再次跳转。
流程图
flowchart TD
A[ARouter 跳转动态模块页面] --> B[路由表未命中 -> onLost]
B --> C[触发 SplitInstallManager 下载]
C --> D[下载完成 -> 安装]
D --> E[重新初始化 LogisticsCenter]
E --> F[再次 navigation -> 成功]
精简源码
java
ARouter.getInstance().build("/dynamic/FeatureActivity")
.navigation(this, new NavigationCallback() {
@Override
public void onLost(Postcard postcard) {
SplitInstallRequest request = SplitInstallRequest.newBuilder()
.addModule("dynamic_feature")
.build();
SplitInstallManager.getInstance(context).startInstall(request)
.addOnSuccessListener(installId -> {
ARouter.getInstance().rebuildRouteTable();
ARouter.getInstance().build(postcard.getPath()).navigation();
});
}
});
Q27:ARouter 的路由分组(group)作用?如何自定义分组并避免冲突?
答案要点
- 分组作用:按需加载分组,减少内存;逻辑隔离。
- 分组规则 :默认取 path 第一段,可显式指定
group。 - 冲突避免 :模块间约定 group 前缀(如
user_、order_)。
流程图
flowchart TD
A[@Route(path = "/user/login")] --> B[默认 group = "user"]
C[ARouter.loadGroup("user")] --> D[仅加载 user 分组路由表]
D --> E[节省内存]
F[显式指定 group = "user_v2"] --> G[避免与其他模块 group 冲突]
精简源码
java
@Route(path = "/order/detail", group = "order_v2")
public class OrderDetailActivity extends AppCompatActivity { }
ARouter.getInstance().build("/order/detail").navigation(null, new NavCallback() {
@Override public void onFound(Postcard postcard) {
ARouter.getInstance().loadGroup("order_v2");
}
});
Q28:多个模块实现同一服务接口,ARouter 如何选择?如何支持条件选取?
答案要点
- 默认行为 :通过
navigation(Class<T>)获取时,Map 覆盖,返回最后加载的实现。 - 多实现管理 :使用不同
path区分,或自定义代理服务。
流程图
flowchart TD
A[同一接口多个实现] --> B{获取方式}
B -->|navigation(接口.class)| C[返回最后加载的实现]
B -->|build(path).navigation()| D[根据path区分返回]
D --> E[支持多实现共存]
精简源码
java
@Route(path = "/print/a")
public class PrinterA implements IPrinter { ... }
@Route(path = "/print/b")
public class PrinterB implements IPrinter { ... }
// 使用
IPrinter printer = (IPrinter) ARouter.getInstance().build("/print/a").navigation();
Q29:ARouter 如何适配新版 ActivityResultContract(替代 onActivityResult)?
答案要点
- 传统方式:内嵌代理 Activity。
- 新版适配 :自定义
ActivityResultContract<Postcard, Bundle>,在createIntent中调用 ARouter 构建 Intent。
流程图
flowchart TD
A[自定义 ActivityResultContract\] --> B[createIntent 调用 ARouter 构建 Intent]
B --> C[registerForActivityResult 注册 launcher]
C --> D[launcher.launch(postcard)]
D --> E[代理 Activity 启动目标]
E --> F[结果通过 parseResult 返回]
精简源码
java
public class ARouterContract extends ActivityResultContract<Postcard, Bundle> {
@NonNull @Override
public Intent createIntent(@NonNull Context context, Postcard postcard) {
return postcard.createIntent(context);
}
@Override
public Bundle parseResult(int resultCode, @Nullable Intent intent) {
return intent == null ? Bundle.EMPTY : intent.getExtras();
}
}
ActivityResultLauncher<Postcard> launcher = registerForActivityResult(new ARouterContract(), result -> {
// 处理返回结果
});
launcher.launch(ARouter.getInstance().build("/order/OrderActivity"));
Q30:ARouter 中的 Postcard 为什么设计为 Serializable?与 Parcelable 对比?
答案要点
- 原因 :Intent 传递要求 extra 实现
Serializable,Postcard需要被代理 Activity 传递。 - 优劣 :
Serializable简单但效率低于Parcelable,但 Postcard 体积小可接受。
流程图
flowchart LR
A[Postcard 需要被 Intent 传递] --> B[Intent 要求 extra 实现 Serializable]
B --> C[Postcard 实现 Serializable]
C --> D[性能低于 Parcelable,但体积小可接受]
Q31:组件间如何基于 EventBus/RxBus 通信?与 ARouter 服务方式优劣对比?
答案要点
- 实现方式 :定义事件实体,发布者
post,订阅者@Subscribe。 - 优劣对比:EventBus 简单一对多但类型不安全;ARouter 服务类型安全但单播。
流程图
flowchart LR
subgraph EventBus
A1[发布者 post事件] --> A2[事件总线] --> A3[所有订阅者接收]
end
subgraph ARouter服务
B1[发布者获取 IEventService] --> B2[调用 dispatchEvent] --> B3[服务内部遍历观察者]
end
精简源码
java
// 事件类
public class LoginSuccessEvent { public String userId; }
// 发布
EventBus.getDefault().post(new LoginSuccessEvent("123"));
// 订阅
@Subscribe(threadMode = ThreadMode.MAIN)
public void onLoginSuccess(LoginSuccessEvent event) { refresh(); }
Q32:ARouter 在大型项目中的局限性有哪些?未来组件化通信的演进方向及替代方案?
答案要点
- 局限:不支持动态卸载、路由表内存常驻、无跨进程路由、跨端支持弱。
- 替代方案:WMRouter(微信)、TheRouter(货拉拉)、Jetpack Navigation。
流程图
flowchart TD
A[ARouter 局限] --> B[不支持动态卸载]
A --> C[路由表内存常驻]
A --> D[无跨进程路由]
A --> E[跨端支持弱]
F[替代方案] --> G[WMRouter: 支持动态加载]
F --> H[TheRouter: 协程+高性能]
F --> I[Jetpack Navigation: 官方组件内导航]
Q33:组件化架构的演进方向(插件化、跨端统一)?ARouter 与 WMRouter/TheRouter 对比?
答案要点
- 演进方向:插件化(动态安装/卸载)、跨端统一(Flutter/RN 桥接)、微前端。
- 对比:ARouter 停更,WMRouter 功能强但文档少,TheRouter 持续更新推荐新项目。
流程图
flowchart TD
A[组件化演进方向] --> B[插件化: 动态安装/卸载组件]
A --> C[跨端统一: Flutter/RN 与原生路由桥接]
A --> D[微前端: Web组件与原生协同]
E[路由方案对比] --> F[ARouter: 停更但稳定]
E --> G[WMRouter: 功能强但文档少]
E --> H[TheRouter: 持续更新,推荐新项目]
精简源码(TheRouter 示例)
kotlin
@Route(path = "/order/detail")
class OrderActivity : AppCompatActivity() {
@Autowired var orderId: String? = null
}
// 调用
TheRouter.build("/order/detail").withString("orderId", "123").navigation()