04-超级App软件平台@路由规则设计-【组件化路由框架详解】

本专题为软件平台路由规则设计 (含移动端、Web、电脑、手表与手环等智能穿戴)。本文对应「组件化路由框架」阶段,介绍基于路由表与注解的组件化解耦方案,涵盖 iOSAndroidHarmonyOSFlutterMacOSWinOSWebAppReactNativeWatchOS 及穿戴设备的实现思路与代码示例,详见 08-软件体系与多平台路由对照


一、概念与定位

1.1 什么是组件化路由框架

多模块、多团队 的工程中,业务模块之间不宜直接依赖具体类(如 import DetailActivity),而应通过统一的路由层 :用 path/URL 表示目标,由路由框架在运行时(或编译期生成表)完成 path → 页面/服务 的映射与跳转。这类以「路由表 + 解耦跳转」为核心的方案,统称组件化路由框架

典型能力包括:路由注册与查找参数传递拦截器链服务发现 (通过 path 获取接口实现)、按分组/模块加载(如按需加载某模块路由表)。

1.2 知识结构(思维导图)

mindmap root((组件化路由框架)) 核心能力 路由注册与查找 参数传递与序列化 拦截器链 服务发现 实现方式 运行时注册 Map 编译期注解 APT 生成表 SPI 按模块加载 平台代表 九端 iOS Android HarmonyOS Flutter MacOS WinOS WebApp ReactNative WatchOS 设计要点 路由表结构 Trie/Map 分组与懒加载 与深链接统一

二、整体架构与数据流(流程图)

flowchart TB subgraph 调用方模块 A[业务代码] A --> B["Router.navigate(path, params)"] end subgraph 路由框架 B --> C[解析 path / query] C --> D[路由表查找] D --> E[拦截器链] E --> F[目标类型 Activity/VC/Widget] F --> G[执行跳转或返回实例] end subgraph 路由表来源 H[iOS: 运行时 register] I[Android: APT 生成 + 分组] J[Flutter: 声明式 routes] H --> D I --> D J --> D end G --> K[目标页面/服务]

三、路由表查找与拦截流程(泳道图)

flowchart LR subgraph 调用方 A1[navigate] end subgraph 路由核心 B1[解析 path] B2[查表] B3[拦截器 1] B4[拦截器 2] B5[执行] end subgraph 目标 C1[Activity/VC/Page] end A1 --> B1 --> B2 --> B3 --> B4 --> B5 --> C1 B3 -.->|onInterrupt| A1 B4 -.->|onInterrupt| A1

四、路由表查找与拦截器链(伪代码)

4.0.1 路由表查找(支持静态 path 与简单动态段)

ini 复制代码
函数 RouteTable.lookup(path, queryParams):
  1. 先精确匹配: 若 table 中存在 key == path,返回 RouteMeta(path, target, type)
  2. 否则遍历「模式」列表(如 /user/:id):
       segments = path 按 '/' 分割
       patternSegments = pattern 按 '/' 分割
       若 length 不同且非通配符,continue
       params = queryParams 的副本
       for i in 0..length-1:
         若 patternSegments[i] 以 ':' 开头:
           params[patternSegments[i].slice(1)] = segments[i]
         否则若 patternSegments[i] != segments[i]: break 内层
       若全部匹配,返回 (RouteMeta, params)
  3. 返回 null

4.0.2 拦截器链执行

sql 复制代码
函数 InterceptorChain.process(context):
  将拦截器按 order 升序排列
  for each interceptor in sortedList:
    ok = interceptor.process(context)
    若 ok 为 false:
      interceptor.onInterrupt(context)
      返回 false
  返回 true

五、iOS 实现详解

5.1 思路:运行时注册 URL → Handler

iOS 常见做法是维护一个 URL → Block 的 Map,在 App 启动或模块加载时注册;跳转时根据 URL 查表执行 Block,在 Block 内创建 ViewController 并 push。

5.2 简单路由管理器(Swift)

swift 复制代码
// Router.swift
import UIKit

typealias RouteHandler = ([String: String]) -> UIViewController?

final class AppRouter {
    static let shared = AppRouter()
    private var routeTable: [String: RouteHandler] = [:]

    func register(path: String, handler: @escaping RouteHandler) {
        routeTable[path] = handler
    }

    func navigate(path: String, params: [String: String] = [:]) -> Bool {
        guard let handler = routeTable[path] else { return false }
        guard let vc = handler(params) else { return false }
        topViewController()?.show(vc, sender: nil)
        return true
    }

    func navigate(url: URL) -> Bool {
        guard url.scheme == "myapp", url.host == "page" else { return false }
        let path = "/" + url.pathComponents.dropFirst().joined(separator: "/")
        let params = url.queryItems ?? [:]
        return navigate(path: path, params: params)
    }

    private func topViewController() -> UIViewController? {
        guard let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow }),
              let root = window.rootViewController else { return nil }
        var top = root
        while let presented = top.presentedViewController { top = presented }
        while let nav = top as? UINavigationController, let visible = nav.visibleViewController { top = visible }
        return top
    }
}

5.3 业务侧注册与调用(Swift)

swift 复制代码
// 在模块初始化或 AppDelegate 中注册
func setupRoutes() {
    AppRouter.shared.register(path: "/page/detail") { params in
        let id = params["id"] ?? ""
        return DetailViewController(itemId: id)
    }
    AppRouter.shared.register(path: "/page/list") { _ in
        return ListViewController()
    }
}

// 业务代码跳转(无依赖 Detail 模块)
AppRouter.shared.navigate(path: "/page/detail", params: ["id": "123"])

5.4 支持 JLRoutes 风格的多段 path(Swift)

swift 复制代码
// 支持 /user/:id/profile 形式
func register(pattern: String, handler: @escaping ([String: Any]) -> Bool) {
    routePatterns.append((pattern: pattern, handler: handler))
}

func navigate(path: String, params: [String: String] = [:]) -> Bool {
    let segments = path.split(separator: "/").map(String.init)
    for (pattern, handler) in routePatterns {
        let patternSegments = pattern.split(separator: "/").map(String.init)
        guard patternSegments.count == segments.count else { continue }
        var captured: [String: Any] = params as [String: Any]
        var match = true
        for (i, p) in patternSegments.enumerated() {
            if p.hasPrefix(":"), !p.isEmpty {
                captured[String(p.dropFirst())] = segments[i]
            } else if p != segments[i] {
                match = false
                break
            }
        }
        if match, handler(captured) { return true }
    }
    return false
}

六、Android 实现详解(ARouter 风格)

6.1 注解与路由表生成思路

通过 APT 在编译期扫描 @Route(path = "/page/detail"),为每个模块生成类似 ARouter$$Group$$main 的类,在初始化时或按分组懒加载时注入路由表。

6.2 路由注解与元数据(Kotlin)

kotlin 复制代码
// 注解定义
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Route(
    val path: String,
    val group: String = "main"
)

// 使用
@Route(path = "/page/detail", group = "page")
class DetailActivity : AppCompatActivity() { ... }

6.3 路由表与查找(Kotlin)

kotlin 复制代码
// RouteMeta.kt
data class RouteMeta(
    val path: String,
    val clazz: Class<*>,
    val type: RouteType = RouteType.ACTIVITY
)

enum class RouteType { ACTIVITY, FRAGMENT, SERVICE }

// Router.kt
object AppRouter {
    private val routeTable = mutableMapOf<String, RouteMeta>()
    private val interceptors = mutableListOf<RouteInterceptor>()

    fun init(context: Context) {
        // 实际 ARouter 会通过 SPI 加载各模块生成的注册类
        // 这里简化:手动注册
        register(RouteMeta("/page/detail", DetailActivity::class.java))
        register(RouteMeta("/page/list", ListActivity::class.java))
    }

    fun register(meta: RouteMeta) {
        routeTable[meta.path] = meta
    }

    fun addInterceptor(interceptor: RouteInterceptor) {
        interceptors.add(interceptor)
    }

    fun build(path: String): Postcard = Postcard(path, routeTable[path])

    fun navigate(context: Context, postcard: Postcard, requestCode: Int = -1) {
        var current = postcard
        for (interceptor in interceptors) {
            if (!interceptor.process(current)) {
                interceptor.onInterrupt(current)
                return
            }
        }
        val meta = current.routeMeta ?: return
        val intent = Intent(context, meta.clazz).apply {
            current.extras?.let { putExtras(it) }
        }
        if (requestCode >= 0) {
            (context as? Activity)?.startActivityForResult(intent, requestCode)
        } else {
            context.startActivity(intent)
        }
    }
}

// Postcard:携带 path、params、extras
class Postcard(val path: String, val routeMeta: RouteMeta?) {
    var extras: Bundle? = null
    fun withString(key: String, value: String) = apply { ... }
}

6.4 业务侧使用(Kotlin)

kotlin 复制代码
// 跳转
AppRouter.navigate(context, AppRouter.build("/page/detail").withString("id", "123"))

// 拦截器示例
class LoginInterceptor : RouteInterceptor {
    override fun process(postcard: Postcard): Boolean {
        return UserManager.isLoggedIn()
    }
    override fun onInterrupt(postcard: Postcard) {
        context.startActivity(Intent(context, LoginActivity::class.java))
    }
}

七、Flutter 实现详解

7.1 声明式路由表 + 统一入口

Flutter 侧可用 go_router 的 path 作为「路由表」,再封装一层 AppRouter,供各模块通过 path 跳转并传参;同时与 Deep Link 共用一套 path 规则。

7.2 路由表与 go_router 配置(Dart)

dart 复制代码
// router_config.dart
import 'package:go_router/go_router.dart';

final goRouter = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(path: '/', builder: (_, __) => HomePage()),
    GoRoute(
      path: '/page/detail/:id',
      builder: (_, state) => DetailPage(id: state.pathParameters['id']!),
    ),
    GoRoute(path: '/page/list', builder: (_, __) => ListPage()),
  ],
);

7.3 统一路由门面(Dart)

dart 复制代码
// app_router.dart
class GlobalRouter {
  static void navigate(String path, {Map<String, String>? params}) {
    final query = params?.entries.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}').join('&');
    final location = query != null && query.isNotEmpty ? '$path?$query' : path;
    goRouter.go(location);
  }

  static void push(String path, {Map<String, String>? params}) {
    final query = params?.entries.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}').join('&');
    final location = query != null && query.isNotEmpty ? '$path?$query' : path;
    goRouter.push(location);
  }
}

7.4 业务侧调用(Dart)

dart 复制代码
// 任意模块内,不直接依赖 DetailPage
GlobalRouter.navigate('/page/detail/123');
GlobalRouter.navigate('/page/detail/123', params: {'from': 'home'});

八、拦截器链时序图

sequenceDiagram participant C as 调用方 participant R as Router participant T as RouteTable participant I1 as 拦截器1 鉴权 participant I2 as 拦截器2 埋点 participant H as 目标页 C->>R: navigate(path) R->>T: lookup(path) T-->>R: RouteMeta R->>I1: process(postcard) alt 未登录 I1-->>C: onInterrupt → 跳转登录 else 通过 I1-->>R: continue R->>I2: process(postcard) I2-->>R: continue R->>H: startActivity / push end

九、组件化路由管理工具类示例(Kotlin)

将「路由表 + 拦截器链 + 跳转」封装成单一门面,便于各模块通过 path 解耦跳转。

kotlin 复制代码
// RouteManager.kt:门面,持有 RouteTable 与 InterceptorChain
class RouteManager(
    private val table: RouteTable,
    private val interceptors: List<RouteInterceptor>,
    private val adapter: PlatformAdapter
) {
    fun navigate(context: Context, path: String, params: Map<String, String> = emptyMap()) {
        val result = table.lookup(path, params) ?: run {
            adapter.openFallback(context)
            return
        }
        val ctx = RouteContext(path, params, result, context)
        for (i in interceptors.sortedBy { it.order }) {
            if (!i.process(ctx)) {
                i.onInterrupt(ctx)
                return
            }
        }
        when (result.type) {
            RouteType.ACTIVITY -> adapter.openPage(context, result, params)
            RouteType.SERVICE -> adapter.resolveService(result, params)
        }
    }
}

// 使用
val manager = RouteManager(table, listOf(LoginInterceptor(), LogInterceptor()), AndroidAdapter())
manager.navigate(context, "/page/detail", mapOf("id" to "123"))

十、九大平台组件化路由要点

平台 路由表形态 典型方案 / API
iOS 运行时 Map、Block/VC 类型 MGJRouter、JLRoutes、自建 Router
Android 注解 APT 生成、运行时注册 ARouter、TheRouter、DRouter
HarmonyOS 路由表或注解、Want 参数 router.pushUrl、页面栈
Flutter GoRoute 列表、自定义表 go_router、GlobalRouter
MacOS 同 iOS 窗口/VC 栈、自建 Router
WinOS 框架路由表 Frame.Navigate、MVVM 路由
WebApp 路由配置表 React Router、Vue Router
ReactNative React Navigation 配置 linking、navigation.navigate
WatchOS 简化 Map 少量界面名映射

十一、小结

  • 组件化路由框架 用 path/URL 解耦模块间依赖,通过路由表 (运行时注册或编译期生成)完成查找,经拦截器链后再执行跳转或服务获取。
  • iOS :可自建 Map<path, Handler> 或使用 JLRoutes/MGJRouter 风格支持动态段;Android :ARouter/TheRouter 通过 APT 生成路由表并支持分组与拦截器;Flutter:go_router 声明 path,再封装 GlobalRouter 统一 navigate。
  • URL Scheme / Universal Links 统一:外链进 App 后解析出的 path + query 应走同一套路由表与拦截器,保证行为一致。

十二、参考文献

  • 阿里 ARouter 原理与 APT.
  • TheRouter 页面跳转能力.
  • 《01-软件平台路由规则设计-总纲》§2.2、§三、§五.
相关推荐
没有故事的Zhang同学2 小时前
07-超级App软件平台@路由规则设计-【通用路由管理组件设计】
程序员
没有故事的Zhang同学6 小时前
02-Debug调试@网络-Wireshark网络抓包工具:从原理到实践
程序员
SimonKing6 小时前
GitHub 10万星的OpenCode,正在悄悄改变我们的工作流
java·后端·程序员
xiezhr7 小时前
36岁程序员被曝复工当晚猝死出租屋内
程序员·996·程序员日常·猝死·加班
xiezhr7 小时前
米哈游36岁程序员被曝复工当晚猝死出租屋内
游戏·程序员·游戏开发
codetown1 天前
2026年Zig编程语言权威指南:从系统级底层架构到现代软件工程实践
后端·程序员
修己xj2 天前
三月,我只想做好这四件事
程序员