10. 2026金三银四 Android 组件化 & ARouter 面试杀手锏:33 道高频题 + 答案 + 流程图 + 源码,资深工程师必刷

19 阅读17分钟

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[完成导航]

精简源码

// 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

精简源码(独立运行开关)

// 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 + 运行时加载)?画出路由表生成与加载流程图。

答案要点

  1. 编译时:APT 扫描 @Route 注解 → 生成 ARouter$$Root$$模块名ARouter$$Group$$分组名ARouter$$Providers$$模块名 等类。
  2. 运行时初始化ARouter.init() → 通过 LogisticsCenter 加载路由表(Dex 扫描或插件注入)→ 存入 Warehouse 静态缓存。
  3. 路由分发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

精简源码

// 编译期生成的 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

精简源码

// 原生显式(强依赖)
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跳转失败]

精简源码

@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[组件作为依赖被宿主集成]

精简源码

// 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 声明接口字段即可获取服务实例。

精简源码

// 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 核心逻辑)

@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. 前缀的类,使用 DexFileClass.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 扫描方式)

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 方法。

精简源码

@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 通过该接口识别服务类,统一管理实例生命周期(初始化→复用→销毁),确保服务全局唯一。

精简源码

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 方法]

精简源码

// 公共接口
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),会调用 SerializationServiceparseObject 方法将 Intent 中传递的字符串反序列化为对象。
  • 实现自定义 SerializationService:实现接口,使用 Gson,并用 @Route 标注。

精简源码

// 自定义类型
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 的字段。

精简源码

@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 添加。
  • 注意事项: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]

精简源码

@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[跳转被阻止]

精简源码

ARouter.getInstance()
    .build("/order/OrderActivity")
    .greenChannel()
    .navigation();

Q17:ARouter 支持 startActivityForResult 吗?如何实现?与原生相比有何限制?

答案要点

  • 支持:通过 navigation(Activity, int)navigation(context, callback, requestCode) 实现。
  • 实现:ARouter 内部创建一个代理 Activity,启动目标,代理 Activity 接收 onActivityResult 并回调给调用方。
  • 限制requestCode 可能被转换;某些 launchModeonActivityResult 可能提前回调;无法传递 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]

精简源码

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]

精简源码

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[避免同名覆盖]

精简源码

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[强制统一版本]

精简源码

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]

精简源码

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-corebase-networkbase-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 核心)

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 路由性能并做非侵入式埋点?

答案要点

  • 监控点:路由表加载耗时、navigationonCreate 时间、拦截器链执行总时长。
  • 非侵入方案:自定义 NavigationCallback 记录时间戳;或使用 AOP 插桩。

流程图

flowchart TD
    A[自定义 NavigationCallback] --> B[记录开始时间戳]
    B --> C[onFound: 记录路由查找耗时]
    C --> D[拦截器执行]
    D --> E[onArrival: 记录完整跳转耗时]
    E --> F[上报监控平台]

精简源码

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]

精简源码

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 -> 成功]

精简源码

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 冲突]

精简源码

@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[支持多实现共存]

精简源码

@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\<Postcard, Bundle\>] --> B[createIntent 调用 ARouter 构建 Intent]
    B --> C[registerForActivityResult 注册 launcher]
    C --> D[launcher.launch(postcard)]
    D --> E[代理 Activity 启动目标]
    E --> F[结果通过 parseResult 返回]

精简源码

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 实现 SerializablePostcard 需要被代理 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

精简源码

// 事件类
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 示例)

@Route(path = "/order/detail")
class OrderActivity : AppCompatActivity() {
    @Autowired var orderId: String? = null
}
// 调用
TheRouter.build("/order/detail").withString("orderId", "123").navigation()