KRouter 在 Decompose 之上补齐声明式路由能力,通过 KSP 在编译期生成路由表,提供:
- 零样板的路由注册(@KRoute 注解 + 自动生成)
- 类型安全的路径跳转与 KBundle 参数传递
- 页面 Result 回调(pushForResult / postResult)
适用于采用 Decompose 做导航的 KMP 项目。

页面栈、生命周期、状态恢复与返回手势由 Decompose 负责;KRouter 只负责「路径 + 参数 → 组件实例」的映射与导航 API。
一、为什么需要 KRouter
导航方案对比
| 方案 | 问题 |
|---|---|
| Compose Navigation | KMP 支持有限(部分平台缺少 BackHandler 与预测性返回手势) |
| Decompose | 没有路径路由,「路径 → 组件」映射、参数传递、结果回调均需自建 |
| KRouter | 在 Decompose 上补齐声明式路由 |
Compose Multiplatform 官方文档将 Decompose 列为支持完整生命周期与依赖注入的高级导航方案;而 Decompose 本身不提供路由层,因此使用时仍需自行解决:
- 路由表手写:每增一页就要手动登记路径与组件的对应关系,漏登记则跳转失败。
- 参数传递无统一抽象:要么构造函数传大量参数,要么自维护参数协议,类型安全与可维护性一般。
- 缺少页面结果约定:如从设置页返回并带回数据,需自建回调或事件机制。
KRouter 补全上述三点。
二、核心设计
KSP 路由生成
页面只需一行注解:
kotlin
@KRoute("/home")
class HomeComponent(ctx: ComponentContext) : KRouterComponent(ctx)
KSP 在编译期扫描所有 @KRoute 类,生成完整的注册文件:
typescript
object GeneratedRouteTable {
fun register() {
KRouter.registerRoute("/home") { ctx -> HomeComponent(ctx) }
KRouter.registerRoute("/login") { ctx -> LoginComponent(ctx) }
KRouter.registerRoute("/register") { ctx -> RegisterComponent(ctx) }
...
}
}
运行时在根组件 init 中调用一次 GeneratedRouteTable.register() 即可,无需手写路由表,无反射。
KBundle 参数序列化
KBundle 是 KRouter 的参数容器,支持基础类型与可序列化对象:
| 操作 | 写入 | 读取 |
|---|---|---|
| 字符串 | putString("key", value) |
getString("key") |
| 整型 | putInt("key", value) |
getInt("key") |
| 长整型 | putLong("key", value) |
getLong("key") |
| 对象 | putObject("key", value) |
getObject<T>("key") |
对象类型须标注 @Serializable:
kotlin
@Serializable
data class User(val id: String, val name: String)
// 跳转时写入
KRouter.push(RoutePath.HOME) {
putObject("user", User("1", "Alice"))
}
// 目标页读取
val user = getBundle().getObject<User>("user")
KBundle 与 KRouteConfig 参与 Decompose 的栈序列化,进程被系统回收后重新进入时,参数可完整还原,无需额外处理。
三、快速开始
3.1 依赖配置
需 Kotlin 1.9+、Compose Multiplatform、Decompose 3.x、KSP。
在共享模块(如 shared)的 build.gradle.kts 中添加:
scss
plugins {
kotlin("multiplatform")
kotlin("plugin.serialization")
id("com.android.library") // 有 Android 就加
id("org.jetbrains.compose")
id("com.google.devtools.ksp")
}
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
api("io.github.lx-0713:krouter:1.0.1")
// Decompose 的 Compose 扩展:根组件用 Children 渲染子页、stackAnimation 做转场,需单独依赖
api("com.arkivanov.decompose:extensions-compose:3.1.0")
}
}
}
}
dependencies {
add("kspCommonMainMetadata", "io.github.lx-0713:krouter-compiler:1.0.1")
}
// 使 KSP 生成代码对 commonMain 可见
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().configureEach {
if (name != "kspCommonMainKotlinMetadata") dependsOn("kspCommonMainKotlinMetadata")
}
kotlin.sourceSets.commonMain {
kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
}
项目需配置 mavenCentral() 仓库。
3.2 路径常量
集中管理路径,便于维护与重构:
kotlin
object RoutePath {
const val LOGIN = "/login"
const val HOME = "/home"
const val DETAIL = "/detail"
const val SETTINGS = "/settings"
}
3.3 页面组件
每个页面继承 KRouterComponent 并标注 @KRoute:
kotlin
// 无参数页面
@KRoute(RoutePath.SETTINGS)
class SettingsComponent(ctx: ComponentContext) : KRouterComponent(ctx) {
@Composable
override fun Content() {
Column {
Text("设置")
Button(onClick = { KRouter.pop() }) { Text("返回") }
}
}
}
// 带基础类型参数
@KRoute(RoutePath.DETAIL)
class DetailComponent(ctx: ComponentContext) : KRouterComponent(ctx) {
private val itemId: String = getBundle().getString("itemId")
@Composable
override fun Content() {
Text("商品 ID: $itemId")
Button(onClick = { KRouter.pop() }) { Text("返回") }
}
}
// 带对象参数(须 @Serializable)
@Serializable
data class User(val username: String)
@KRoute(RoutePath.HOME)
class HomeComponent(ctx: ComponentContext) : KRouterComponent(ctx) {
private val user: User = getBundle().getObject<User>("user") ?: User("未知")
@Composable
override fun Content() {
Text("欢迎,${user.username}!")
}
}
3.4 根组件
init 中注册路由表并创建栈;Content() 中用 Children 渲染栈顶并配置转场:
kotlin
class RootComponent(componentContext: ComponentContext) : KRouterComponent(componentContext) {
val childStack: Value<ChildStack<KRouteConfig, KRouterComponent>>
init {
GeneratedRouteTable.register()
childStack = KRouter.createChildStack(this, RoutePath.LOGIN)
}
@Composable
override fun Content() {
MaterialTheme {
Children(
stack = childStack,
animation = stackAnimation(slide()),
onBack = { KRouter.popImmediate() }
) {
Surface(modifier = Modifier.fillMaxSize()) {
it.instance.Content()
}
}
}
}
}
四、导航 API
scss
// 跳转新页面
KRouter.push(RoutePath.DETAIL) { putString("itemId", "123") }
// 跳转,栈中已有该路径则不重复压栈
KRouter.pushNew(RoutePath.SETTINGS)
// 替换当前页
KRouter.replaceCurrent(RoutePath.HOME) { putString("from", "detail") }
// 清空栈并跳转(登录成功、退出登录等场景)
KRouter.replaceAll(RoutePath.HOME) { putObject("user", user) }
// 返回
KRouter.pop()
KRouter.popImmediate() // 响应系统返回键/手势时使用
KRouter.popTo(0) // 退到栈中指定层(0 为栈底)
// 将某路径的页面提到栈顶,不存在则新建
KRouter.bringToFront(RoutePath.HOME) { putString("refresh", "1") }
参数通过 KBundle 传递;传对象时类型须 @Serializable。
五、Result 机制
发起页 :pushForResult 跳转,重写 onComponentResult 接收结果:
kotlin
@KRoute(RoutePath.HOME)
class HomeComponent(ctx: ComponentContext) : KRouterComponent(ctx) {
private var settingsResult: KBundle? by mutableStateOf(null)
private fun goToSettings() = pushForResult(RoutePath.SETTINGS)
override fun onComponentResult(bundle: KBundle) {
settingsResult = bundle
}
@Composable
override fun Content() {
Column {
settingsResult?.let { Text("设置页返回: ${it.getString("msg")}") }
Button(onClick = { goToSettings() }) { Text("打开设置") }
}
}
}
目标页 :返回前 postResult 写入数据,再 pop:
less
Button(onClick = {
KRouter.postResult { putString("msg", "用户修改了字体大小") }
KRouter.pop()
}) {
Text("携带数据返回")
}
结果接收方由 KRouter 内部维护,组件销毁时自动移除,不会产生误回调或泄漏。
六、小结
KRouter 在 Decompose 之上补全路由与参数、结果能力,适用于采用 Decompose 做导航的 KMP 项目。更多 API 与配置见 GitHub 。