设计路由框架
问题
如何从零设计一个 Android 路由框架?
答案
核心需求
- 页面跳转:通过 URL /路径跳转到目标 Activity/Fragment
- 参数传递:类型安全的参数注入
- 拦截器:登录检查、权限验证
- 降级策略:路由未找到时的降级处理
架构设计
实现要点
// 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 插件注入的方式,兼顾性能和易用性。