shared/
└── src/
├── commonMain/ ← 跨端共享代码(90%)
│ └── kotlin/
│ └── 你写的 Pager / 业务逻辑 / 数据模型
│
├── androidMain/ ← Android 平台特有
│ └── 调 android.* 的代码(actual 实现)
│
├── iosMain/ ← iOS 平台特有
│ └── 调 platform.UIKit.* 的代码
│
├── ohosArm64Main/ ← HarmonyOS 平台特有
│
├── jsMain/ ← Web / 小程序平台特有
│
└── commonTest/ ← 跨端测试
当某个 API 必须各端不同实现时,用 expect 在 commonMain 声明,各 platformMain 用 actual 实现。
// commonMain/PlatformInfo.kt
expect fun currentPlatform(): String
// androidMain/PlatformInfo.kt
actual fun currentPlatform(): String =
"Android ${Build.VERSION.RELEASE}"
// iosMain/PlatformInfo.kt
actual fun currentPlatform(): String =
"iOS ${UIDevice.currentDevice.systemVersion}"
// jsMain/PlatformInfo.kt
actual fun currentPlatform(): String =
"Web ${window.navigator.userAgent}"
业务侧调用方式跟普通函数一样:
// 业务代码(commonMain)
val plat = currentPlatform()
println("Hello from $plat")
// Android 跑:Hello from Android 14
// iOS 跑:Hello from iOS 17.0
// Web 跑:Hello from Web Mozilla/5.0...
| 动作 | 谁做 | 产物 |
|---|---|---|
| 扫描 @Page | KSP processor | PagerRegistry 注册代码 |
| 扫描 @Component | KSP processor | 组件工厂代码 |
| 生成入口 | KSP processor | KuiklyCoreEntry.triggerRegisterPages() |
// 你写的:
@Page("HomePage")
class HomePage : Pager() { ... }
@Page("DetailPage")
class DetailPage : Pager() { ... }
// KSP 编译期生成(你看不到,但确实存在):
object KuiklyCoreEntry {
fun triggerRegisterPages() {
PagerRegistry.register("HomePage") { HomePage() }
PagerRegistry.register("DetailPage") { DetailPage() }
}
}
同一份 DSL,两棵树长不一样:
View {
attr { allCenter() }
Text { attr { text("A") } }
if (showB) Text { attr { text("B") } }
vfor({ list }) { Text { ... } }
}
每个 RenderTree 节点都跟一个原生 View 1:1 对应。Diff 算法只关心"渲染相关"的变化,效率更高。
| 框架 | 更新粒度 | 触发方式 |
|---|---|---|
| Vue 2 | 组件级 | watcher 触发组件 re-render |
| React (默认) | 组件级 | setState → 整组件 + 子树 re-render |
| SolidJS | 表达式级 | signal getter 追踪 → 仅相关 effect 重跑 |
| Kuikly | attr 块级 | observable 追踪 → 仅相关 attr 块重跑 |
所有 UI 操作 → 6 类核心指令 → 通过 Bridge 下发到各端原生层
| 指令 | 参数 | 作用 |
|---|---|---|
CreateView | viewId, viewType, parentId | 创建一个原生 View |
SetProp | viewId, key, value | 修改 View 的某个属性 |
AddEventListener | viewId, event | 给 View 注册事件监听 |
RemoveEventListener | viewId, event | 移除事件监听 |
AddSubview | parentId, childId, index | 把 child 挂到 parent |
RemoveSubview | parentId, childId | 从 parent 移除 child |
下面的 DSL 转成的指令序列:
View {
attr { backgroundColor(Color.WHITE); size(100f, 50f) }
Text { attr { text("Hello") } }
}
每个平台 Bridge 实现不同,但接口语义统一:
| 平台 | 实现方式 | 开销 |
|---|---|---|
| Android | JNI + 直接 JVM 调用 | 极小(普通函数调用) |
| iOS | Kotlin/Native ↔ ObjC 互操作 | 极小 |
| HarmonyOS | Kotlin/Native ↔ ArkTS NAPI | 较小 |
| Web | 同语言(Kotlin/JS) | 无 |
| 小程序 | Kotlin/JS + setData | 中(受小程序架构限制) |
★ 整个过程在一帧 16ms 内完成。Android 端因为是 JVM 直接调用,几乎零桥接开销。
Dart → Flutter Engine(C++)→ Skia → OpenGL/Metal → 屏幕
★ 不依赖原生 UI,跨端一致性强
★ 引擎包体大(5MB+)
★ 系统组件需自己模拟
Kotlin → KMP 编译 → 跨端核心层 → 渲染指令 → 各端原生 View
★ 复用各平台原生 UI,体验真原生
★ 包体小(300KB-1MB)
★ 系统组件直接用,无需模拟
| 维度 | Kuikly | Flutter | React Native |
|---|---|---|---|
| 开发语言 | Kotlin | Dart | JavaScript |
| 渲染方式 | 原生控件 | Skia 自绘 | 原生控件 |
| SDK 体积 (Android) | ~300KB | ~5MB | ~3MB |
| 桥接开销 | 极小(同语言/JNI) | 无(自绘) | 大(JS Bridge 序列化) |
| 跨端一致性 | 中(保留平台差异) | 高(自己画) | 中(受原生影响) |
| 真原生体验 | ✅ | ⚠️ 模拟 | ✅ |
| 鸿蒙支持 | ✅ 一等公民 | 第三方插件 | 第三方插件 |
| 小程序 | ✅ 原生支持 | ❌ | ❌ |