因为时间紧张,KRouter 的第一个版本是在仓促中开发出来的,功能勉强可用而已。最近我有空闲时间重新设计和优化了它,增加了对 Kotlin Multiplatform 和参数注入的支持的功能。实现也被彻底重构了,用 KSP 替换了 ServiceLoader 来收集路由信息。它已经发布到了 Maven Central Repository。
GitHub - 0xZhangKe/KRouter: 轻量且简单的 Kotlin 路由器,基于 ServiceLoader 和 KSP 的。用于 Kotlin 模块间通信。……github.com作为一个路由框架,它不仅要能够收集路由,还要能将收集到的路由信息提供给路由系统。这就是我为什么在第一个版本中使用了 ServiceLoader 的原因。所以在开发这个新版本时,我最初计划使用 KCP 修改字节码来实现这一功能。经过几天的研究,我发现这种方法还是不行。最后卡在了 KCP 无法读写依赖模块的类文件的问题上。我现在知道的是,KCP 只能读写当前模块的文件,不能读写依赖模块的类文件。KSP 也通过一些复杂的方式实现了类似的功能,相关的文档内容贫乏,不够详细,所以我决定放弃。如果有其他解决方法或建议,请不吝告知。
怎么用:依然简单明了,与以前一样。
首先标注目标类:
@Destination("screen/main")
class 主屏幕(@RouteParam("id") val id: String, @RouteParam("name") val name: String): 屏幕
然后,用 KRouter
查找相应的类。
val screen = KRouter.route<Screen>("screen/main?name=zhangke&id=123")
// 屏幕的路由配置
KRouter 目前支持三种注解标签:@Destination
,@RouteUri
,和 @RouterParam
。
正如其名,@Destination
注解标记了路由的目标类,也就是目的地,带有一个参数,表示路由目标地址。
@目标(AnnotationTarget.CLASS::class)
@保留(AnnotationRetention.RUNTIME::class)
annotation class Destination(val route: String)
它可以这样用:
@Destination("屏幕/资料详情/详情")
class ProfileDetailScreen 屏幕类
RouterParam
@RouterParam
注解用来标记路由中的参数。被这个注解标记的字段将会自动填充对应的路由参数值。
它还接收一个参数,表示路由中查询字段的名称。KRouter 会根据该字段名解析并赋值。
// 路由参数注解,用于标记路由参数名称
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class RouteParam(val name: String)
这里有一个使用它的例子:
@Destination("screen/home/detail")
class HomeDetailScreen(
@RouteParam("id") val id: String,
) : Screen {
// 具有id参数的家庭详情屏幕
}
除了支持构造函数参数注入外,还支持属性注入。
@Destination("screen/home/detail")
class HomeDetailScreen(
@RouteParam("id") val id: String, // 页面标识id
) : Screen {
@RouteParam("title") var title: String? = null // 标题
}
目前,参数类型目前仅限于基本类型和 String
。对于更复杂的类型,你可以将对象转换为 JSON 字符串,然后将其编码进路由。
请留意,KSP 当前无法获取参数和属性的默认值。因此,注入字段不能设置默认值。这意味着,如果你设置了注入字段的默认值,而路由中没有包含这个参数,那么这个默认值就会被忽略。
技术术语:路由URI带有此注解的参数或属性将被赋值为完整的路由路径。由于@RouterParam
仅适用于字段注入,对于更复杂的解析情况,可以使用@RouteUri
来获取完整的路由路径。
KRouterModule
是一个用于实现特定路由功能的接口,它。你可以通过 KRouter
类动态添加自定义模块。默认情况下,它会优先使用动态添加的模块;如果路由失败,则会退回到 KRouter 自带的路由。
接口 KRouterModule {
fun 路由(uri: String): Any? // 路由到指定的URI并返回任意类型的结果
}
添加依赖
KRouter 提供了两个 KSP 插件。
krouter-collecting-compiler
: 收集路由相关的数据,用于非主模块的。krouter-reducing-compiler
: 汇总来自各个模块的路由信息,仅用于主模块(如应用程序模块)。
// 在非主模块中使用
ksp("io.github.0xzhangke:krouter-collecting-compiler:$latest_version")
// 在主模块中使用
ksp("io.github.0xzhangke:krouter-reducing-compiler:$latest_version")
此外,还有一个注解模块和运行时组件。
-
krouter-runtime
: 运行时模块,提供KRouter
、KRouterModule
注解及相关的注解支持。 krouter-annotation
: 注解模块,仅包含注解,没有提供任何类。
// 仅需使用注解的模块
implementation("io.github.0xzhangke:krouter-runtime:$latest_version")
// 需要路由功能的模块可以直接
implementation("io.github.0xzhangke:krouter-annotation:$latest_version")
实现细节
首先,collecting-compiler
插件(编译器)收集模块内所有路由目标类的信息,并生成一个属于当前模块的 KRouterModule
。生成的类可能如下:
public class RouterCollection_1726153189290() : KRouterModule {
override fun route(uri: String): Any? {
val routerUri = com.zhangke.krouter.internal.KRouterUri.create(uri)
return when (routerUri.baseUrl) {
"screen/home/detail" -> {
com.zhangke.krouter.sample.home.HomeDetailScreen(
id = routerUri.requireQuery("id"),
)
}
"screen/home/landing" -> {
com.zhangke.krouter.sample.home.HomeLandingScreen(
router = uri,
)
}
else -> null
}
}
}
所有依赖于 collecting-compiler
插件的模块,都会生成这样一个类。
然后,在主项目模块(通常是 app 模块),需要引入 reducing-compiler
插件。此插件生成一个固定包名和类名的类,收集 collecting-compiler
生成的全部类,并将这些类添加至新类中。
public class AutoReducingModule() : KRouterModule {
private val moduleList: List<KRouterModule> = listOf<KRouterModule>(
com.zhangke.krouter.generated.RouterCollection_1726153189283(),
com.zhangke.krouter.generated.RouterCollection_1726153189290(),
com.zhangke.krouter.generated.RouterCollection_1726153189284(),
com.zhangke.krouter.generated.RouterCollection_1726153189709()
)
override fun route(uri: String): Any? = moduleList.firstNotNullOfOrNull { it.route(uri) }
}
运行时,KRouter 通过反射创建该类,并将路由处理交给该类中的实现来处理。这样就完成了整个路由的收集和实现。