跳到主要内容

设计路由框架

问题

如何从零设计一个 Android 路由框架?

答案

核心需求

  1. 页面跳转:通过 URL /路径跳转到目标 Activity/Fragment
  2. 参数传递:类型安全的参数注入
  3. 拦截器:登录检查、权限验证
  4. 降级策略:路由未找到时的降级处理

架构设计

实现要点

// 1. 路由注解
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Route(
val path: String,
val group: String = "",
val needLogin: Boolean = false
)

// 2. 路由元信息
data class RouteMeta(
val path: String,
val clazz: Class<*>,
val type: RouteType, // ACTIVITY, FRAGMENT, SERVICE
val needLogin: Boolean = false,
val extras: Bundle = Bundle()
)

// 3. 路由核心
object Router {
private val routeTable = ConcurrentHashMap<String, RouteMeta>()
private val interceptors = mutableListOf<RouteInterceptor>()

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

fun navigate(context: Context, path: String, params: Bundle = Bundle()): Boolean {
val meta = routeTable[path] ?: run {
// 降级处理
onRouteMissing(context, path)
return false
}

// 执行拦截器链
val chain = InterceptorChain(interceptors, meta, params)
if (!chain.proceed()) return false

// 跳转
when (meta.type) {
RouteType.ACTIVITY -> {
val intent = Intent(context, meta.clazz).apply { putExtras(params) }
context.startActivity(intent)
}
RouteType.FRAGMENT -> { /* 返回 Fragment 实例 */ }
}
return true
}
}

编译期路由表生成(KSP)

// KSP 处理器在编译期扫描 @Route 注解,生成路由注册代码
// 生成类似以下代码:
object RouteTable_home : IRouteGroup {
override fun register(table: MutableMap<String, RouteMeta>) {
table["/home/main"] = RouteMeta(
path = "/home/main",
clazz = HomeActivity::class.java,
type = RouteType.ACTIVITY
)
}
}

常见面试问题

Q1: 路由框架的编译期方案和运行时方案有什么区别?

答案

  • 编译期(APT/KSP):在编译时扫描注解,生成路由表注册代码。优点是启动无反射开销,缺点是编译时间增加
  • 运行时(反射扫描):应用启动时扫描 dex 文件,查找带有路由注解的类。优点是实现简单,缺点是首次启动耗时

主流框架(ARouter、TheRouter)都采用编译期生成 + Gradle 插件注入的方式,兼顾性能和易用性。

相关链接