Android TheRouter 笔记

17 阅读11分钟

TheRouter是货拉拉开源的一套专门面向模块化/组件化开发的一整套解决方案框架。它的设计理念比较超前,不仅解决了页面跳转的解耦问题,还顺带把模块化开发中常见的依赖注入、模块初始化、动态化配置等问题都给系统地解决了。

简单来说,TheRouter的核心功能可以概括为四大块,我们可以一个个来看:

📦 TheRouter的四大核心功能

  • Navigator(页面导航):这是路由最基础的能力,用于解耦模块间的页面跳转依赖。但它做得很细:

    • 多对一路由:支持多个 path 指向同一个页面(Activity/Fragment)。这个特性对于双端(Android/iOS)路由表统一很有用,能降低多端对齐的成本。
    • 动态路由表:这是它的一个亮点。允许通过远端下发JSON配置来动态修改路由表。比如线上某个原生页面出了严重Bug,可以直接下发配置,将这个页面的跳转目标临时降级为一个H5页面,实现快速的“容灾”。
    • 参数传递:除了Intent支持的基本类型,还可以通过 withObject() 传递任意对象,甚至支持在路由表中为页面预设默认参数。
    • 拦截器体系:提供了 PathFixHandler(修复Path)、PathReplaceInterceptor(替换Path)、NavigatorInterceptor(路由拦截)等多层级的拦截机制,可以在跳转前/后插入各种逻辑,比如登录拦截。
  • ServiceProvider(服务依赖注入):这是为了解决模块间功能调用的问题。它参考了SOA(面向服务架构)的设计思想,让模块之间只依赖接口,而不依赖具体的实现类。

    • 接口隔离:比如订单模块需要获取用户信息,它只需要依赖一个 IUserService 接口,而不用关心这个接口是哪个模块实现的。
    • 服务注册与发现:服务提供方通过 @ServiceProvider 注解标记一个静态方法或类,将服务实现“注册”到框架中。服务使用方则通过 TheRouter.get(IUserService::class.java) 来“发现”并使用这个服务。
    • 灵活的对象管理:你可以通过 @Singleton@NewInstance 等注解来控制服务对象的创建模式(单例、每次都新建或默认的LRU缓存),这在性能和状态管理上给了开发者很大的自由度。
  • FlowTaskExecutor(单模块自动初始化):在模块化项目里,每个模块可能都需要在Application启动时做一些初始化工作。传统做法是在Application里手动调用各个模块的初始化方法,耦合度高且难以维护。

    • 任务化:TheRouter把这个过程抽象成了一个个的 FlowTask。你只需要在任意类的方法上加上 @FlowTask 注解,并声明它的任务名和依赖关系(比如 dependsOn = TheRouterFlowTask.APP_ONCREATE),框架就会在恰当的时机(例如Application的onCreate()之后)自动、按依赖顺序初始化这些模块。
    • 循环依赖检测:它甚至在编译期就能检测出任务之间的循环依赖,避免运行时死锁。
  • ActionManager(动态化能力):这是一个全局的、类似事件总线的机制,但又比普通的观察者模式更强大。

    • 链式响应与优先级:你可以定义某个 Action(通过一个URI标识),并指定它的响应者。当这个Action被触发时,多个响应者可以按照优先级链式处理。
    • 可追踪性:它能记录整个调用路径,这对于调试那些“不知道谁调用了谁”的复杂业务逻辑来说,简直是神器。

⚙️ 实现原理深度剖析

TheRouter之所以功能强大且性能优异,关键在于它巧妙地在编译期做了大量工作,最大程度地避免了运行时的性能损耗。

  • 编译期:代码生成与聚合

    1. 注解处理:在编译时,通过APT(注解处理工具)或更高效的KSP(Kotlin Symbol Processing),扫描所有模块中被 @Route@ServiceProvider@FlowTask 等注解标记的代码。
    2. 生成中间代码:针对每个模块,它会生成一个 RouteMap__ 开头的Java类。这个类里面包含了两个核心部分:
      • 一个静态常量字符串 ROUTERMAP,记录了该模块的路由信息(如pathclassNameparams)的JSON格式。
      • 一个静态方法 addRoute(),里面是一系列直接调用框架API的代码,用于将该模块的路由项添加到全局路由表中。
    3. Gradle插件聚合:最后,通过自定义的Gradle插件,它会遍历所有依赖的模块(包括AAR和源码),找到所有 RouteMap__ 类,并将它们的 addRoute() 方法统一收集到一个名为 TheRouterServiceProvideInjecter 的类中。
  • 运行时:无反射加载与高效跳转

    1. 无反射初始化:在应用启动调用 TheRouter.init() 时,框架内部会直接调用 TheRouterServiceProvideInjecter 类中的方法。由于这些方法是编译期生成的直接调用,整个过程完全没有使用反射,因此初始化速度非常快。
    2. 路由表存储:所有路由信息会被加载到一个支持正则表达式匹配的 Map 结构中。这也是它能实现 path 一对多的技术基础。
    3. 跳转流程:当调用 TheRouter.build(path).navigation() 时,整个过程大致如下:
      • 创建Navigatorbuild() 方法会创建一个 Navigator 对象。在构造器中,会先执行开发者设置的 PathFixHandler,对原始的 path 进行修正。
      • 解析参数:接着,解析 path 中的query参数(如 ?key=value),并将其存储到 Navigator 内部的 Bundle 中。
      • 查找路由项:当调用 navigation() 时,框架会用最终的 path 去那个支持正则的 Map 中查找匹配的 RouteItem
      • 执行拦截器与跳转:如果找到了目标页面,会依次执行 PathReplaceInterceptorNavigatorInterceptor,最后通过 startActivity()startActivityForResult() 完成最终的跳转。

🆚 横向对比

TheRouter在设计之初就对标了业内主流的ARouter和WMRouter,解决了很多痛点。这里有一个简单的对比:

  • 性能:TheRouter在编译期通过Gradle插件聚合代码,实现了无运行时扫描、无反射的加载方式,性能损耗最小。相比之下,ARouter在运行时需扫描Dex并反射实例化类,WMRouter也需运行时读文件和反射。
  • 动态性:TheRouter原生支持远端路由表下发,可用于页面降级容灾,这是它的一大特色。其他两者则不具备或支持较弱。
  • 功能完整性:除了基础路由,TheRouter还内置了服务注入、模块自动初始化、ActionManager等一整套模块化解决方案。其他框架则主要集中在路由本身。
  • 对热修复友好:由于采用了独特的代码生成策略,对于未改动路由表的模块,多次编译生成的中间代码不会有变化,因此不会产生无意义的热修复补丁体积。
  • 易用性:支持KSP编译,编译速度比kapt更快。同时支持多Path对应同一页面,方便双端路由统一。

TheRouter是一套为企业级模块化开发量身打造的解决方案。它通过在编译期下功夫换来了运行时的高性能,并通过丰富的功能集,将模块化过程中的页面跳转、服务调用、模块初始化、动态配置等问题统一了起来。特别是它的动态路由下发能力,为应用的线上容灾提供了很实用的手段。

PathFixHandler 是 TheRouter 中一个非常实用的轻量级拦截点,用于在路由构建的最早期对原始 path 进行统一修正。它属于框架中“路径修复”这一层,比 PathReplaceInterceptor 执行得更早,并且专注于格式规范化默认值补全。下面我们来详细拆解它的实现原理和替换机制。


1. PathFixHandler 的定义与作用

PathFixHandler 是一个函数式接口(通常只有一个方法),定义大致如下:

public interface PathFixHandler {
    String fix(String originalPath);
}

它的作用是接收原始 path 字符串,返回一个修复后的新 path 字符串。常见的使用场景包括:

  • 补全 Scheme:例如将 "home" 自动补全为 "therouter://home"
  • 统一大小写/格式:将 "user/profile" 统一为小写 "user/profile"
  • 旧版路径兼容:将旧版本的 "old/home" 映射为 "home"
  • 环境切换:根据 BuildConfig 动态添加环境前缀(如 "dev/""prod/")。

与拦截器的区别在于:PathFixHandler 发生在路由构建的最开始,仅对字符串做变换,不涉及路由表查找或拦截决策。


2. PathFixHandler 的注册方式

TheRouter 允许开发者通过全局 API 添加多个 PathFixHandler,通常是在应用初始化时进行:

// 添加一个补全 Scheme 的 Handler
TheRouter.addPathFixHandler(path -> {
    if (!path.contains("://")) {
        return "therouter://" + path;
    }
    return path;
});

// 可以继续添加其他 Handler
TheRouter.addPathFixHandler(path -> path.toLowerCase(Locale.US));

这些 Handler 会被存储在一个 List<PathFixHandler> 中,按照添加顺序排列。


3. 调用链机制:链式顺序执行

当调用 TheRouter.build(path) 时,会创建一个 Navigator 对象。在 Navigator 的构造器中,会立即对所有已注册的 PathFixHandler 进行链式调用

public Navigator(String path) {
    // 1. 获取全局的 PathFixHandler 列表
    List<PathFixHandler> fixHandlers = TheRouter.getPathFixHandlers();
    
    // 2. 依次应用修复
    String fixedPath = path;
    for (PathFixHandler handler : fixHandlers) {
        fixedPath = handler.fix(fixedPath);
    }
    
    // 3. 将修复后的 path 保存到 Navigator 内部
    this.path = fixedPath;
    
    // 4. 解析 query 参数等后续操作...
}

这意味着:

  • 每个 Handler 都会接收到上一个 Handler 处理后的结果。
  • 最终的 path 将作为后续路由查找和跳转的依据。
  • 如果某个 Handler 返回 null,则会中断后续处理并导致 build() 失败(通常框架会使用原始 path 或者抛出异常,具体取决于实现)。

这种设计保证了修复逻辑的可组合性顺序性


4. 替换逻辑的深入分析

“替换”的本质就是字符串变换。开发者可以在 fix() 方法中实现任意复杂的逻辑,例如:

  • 正则替换:将匹配特定模式的部分替换为其他内容。
  • 查表映射:根据一个预定义的 Map 进行路径映射。
  • 动态下发配置:从远端获取的配置中查找替换规则。

由于 PathFixHandler 在 build() 阶段执行,此时尚未解析参数,因此无法直接访问 Navigator 中的参数 Bundle,但可以通过全局状态(如 SharedPreferences)或依赖注入获取配置。

示例:根据远端配置进行路径替换

假设我们有一个远端下发的 JSON,定义了某些旧路径需要重定向到新路径:

{
  "path_redirect": {
    "/old/user": "/user/profile",
    "/old/order": "/order/list"
  }
}

可以在 PathFixHandler 中加载这个配置:

TheRouter.addPathFixHandler(path -> {
    Map<String, String> redirectMap = ConfigManager.getPathRedirectMap();
    String target = redirectMap.get(path);
    return target != null ? target : path;
});

这样所有符合配置的旧路径都会被自动替换为新路径。


5. PathFixHandler 与 PathReplaceInterceptor 的对比

为了更清晰地理解职责划分,这里做一个对比:

特性PathFixHandlerPathReplaceInterceptor
执行时机build() 构造器中,查找路由表之前navigation() 中,查找路由表之前
输入输出输入原始 path,输出修复后的 path输入原路由项和目标 path,输出新的路由项或 path
典型用途格式规范化、Scheme补全、默认值模块化解耦、根据业务状态动态替换目标页面
能否访问路由表不能可以(因为已加载路由表)
是否影响参数不影响(仅 path 字符串)可以修改参数(通过替换路由项)

简单来说,PathFixHandler 更偏向于字符串预处理,而 PathReplaceInterceptor 更偏向于路由项的动态替换


6. 源码级实现要点

在 TheRouter 的源码(以 v1.x 为例)中,TheRouter.java 内部维护了一个 CopyOnWriteArrayList<PathFixHandler>,保证线程安全。addPathFixHandler 方法将 handler 添加到此列表。build(String path) 方法内部会调用 applyPathFix(path) 私有方法,完成上述链式处理。

关键代码片段(伪代码):

public class TheRouter {
    private static final List<PathFixHandler> FIX_HANDLERS = new CopyOnWriteArrayList<>();

    public static void addPathFixHandler(PathFixHandler handler) {
        FIX_HANDLERS.add(handler);
    }

    private static String applyPathFix(String path) {
        String result = path;
        for (PathFixHandler handler : FIX_HANDLERS) {
            result = handler.fix(result);
            if (result == null) {
                // 处理异常,可回退或抛出
                break;
            }
        }
        return result;
    }

    public static Navigator build(String path) {
        String fixedPath = applyPathFix(path);
        return new Navigator(fixedPath);
    }
}

7. 注意事项与最佳实践

  • 不要执行耗时操作:PathFixHandler 在主线程的 build() 中执行,应避免网络请求或复杂计算,否则会影响跳转性能。
  • 保持幂等性:多次应用同一个 handler 不应产生副作用,因为 handler 可能会被重复调用(如多次 build)。
  • 顺序依赖:如果多个 handler 之间有依赖关系,需谨慎安排添加顺序。
  • 避免死循环:如果 handler 中又调用了 TheRouter.build(),可能会触发无限递归,需要避免。
  • 日志与调试:可以借助 TheRouter.setDebuggable(true) 开启日志,观察 path 的修复过程。

8. 总结

PathFixHandler 是 TheRouter 提供的一个轻量级的、可插拔的路径预处理机制。它通过在 build() 阶段对原始 path 进行链式替换,实现了统一格式化、默认值补全、动态映射等能力,且对开发者透明。这种设计体现了“关注点分离”的原则,让路由的核心逻辑保持简洁,同时赋予了框架极高的灵活性和扩展性。

在实际项目中,合理利用 PathFixHandler 可以极大地简化路由管理,尤其是对于大型多模块应用,它能帮助团队统一规范、兼容旧版路径,甚至实现动态降级。