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

11 阅读4分钟

本专题为软件平台路由规则设计(含移动端、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 与简单动态段)

函数 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 拦截器链执行

函数 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)

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

// 在模块初始化或 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)

// 支持 /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)

// 注解定义
@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)

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

// 跳转
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)

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

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

// 任意模块内,不直接依赖 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 解耦跳转。

// 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、页面栈
FlutterGoRoute 列表、自定义表go_router、GlobalRouter
MacOS同 iOS窗口/VC 栈、自建 Router
WinOS框架路由表Frame.Navigate、MVVM 路由
WebApp路由配置表React Router、Vue Router
ReactNativeReact 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、§三、§五.