第 7 章 · 跨端架构原理 — KMP + 两棵树 + Bridge

点击「触发点击事件」按钮,看 Kotlin 层 → 渲染指令 → 原生层的完整流程

三层架构

业务层(你的代码)
@Page("Home") class HomePage : Pager()
100% Kotlin commonMain · 跨 6 端共享
▼ KMP 编译
Kuikly 跨端核心层(框架自己)
DSL 解析 → BuildTree → Diff → RenderTree → 渲染指令
90% Kotlin commonMain · Yoga 布局 · observable 响应式 · KSP 编译期生成
▼ 渲染指令通过 Bridge 下发
各端原生渲染层(每端一份适配器)
Android View · iOS UIView · ArkUI · DOM · WXML · NSView
10% 平台原生代码 · 每端实现 6 大指令的 handler

6 端编译产物

🤖
Android
Kotlin/JVM
.aar
🍎
iOS
Kotlin/Native
.framework
🪐
HarmonyOS
Kotlin/Native
.so
🌐
Web
Kotlin/JS
.js
📱
小程序
Kotlin/JS
.js
🖥️
macOS
Kotlin/Native
.framework

KMP 工程的源码集(SourceSet)结构

shared/
└── src/
    ├── commonMain/         ← 跨端共享代码(90%)
    │   └── kotlin/
    │       └── 你写的 Pager / 业务逻辑 / 数据模型
    │
    ├── androidMain/        ← Android 平台特有
    │   └── 调 android.* 的代码(actual 实现)
    │
    ├── iosMain/            ← iOS 平台特有
    │   └── 调 platform.UIKit.* 的代码
    │
    ├── ohosArm64Main/      ← HarmonyOS 平台特有
    │
    ├── jsMain/             ← Web / 小程序平台特有
    │
    └── commonTest/         ← 跨端测试

expect / actual:跨端 API 标准范式

当某个 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...

KSP:编译期魔法

动作谁做产物
扫描 @PageKSP processorPagerRegistry 注册代码
扫描 @ComponentKSP processor组件工厂代码
生成入口KSP processorKuiklyCoreEntry.triggerRegisterPages()
// 你写的:
@Page("HomePage")
class HomePage : Pager() { ... }

@Page("DetailPage")
class DetailPage : Pager() { ... }

// KSP 编译期生成(你看不到,但确实存在):
object KuiklyCoreEntry {
    fun triggerRegisterPages() {
        PagerRegistry.register("HomePage") { HomePage() }
        PagerRegistry.register("DetailPage") { DetailPage() }
    }
}

两棵树架构 · BuildTree vs RenderTree

同一份 DSL,两棵树长不一样:

View {
    attr { allCenter() }
    Text { attr { text("A") } }
    if (showB) Text { attr { text("B") } }
    vfor({ list }) { Text { ... } }
}

BuildTree(原型树)— 含逻辑节点

View (root)
├─ Text "A"
├─ ConditionNode (showB)
│ └─ Text "B"
└─ ForNode (list)
├─ Text item1
├─ Text item2
└─ Text item3

RenderTree — 剔除逻辑节点

View (root)
├─ Text "A"
├─ Text "B"
├─ Text item1
├─ Text item2
└─ Text item3

Diff 在 RenderTree 上做

每个 RenderTree 节点都跟一个原生 View 1:1 对应。Diff 算法只关心"渲染相关"的变化,效率更高。

跟 RN/Vue 对比:精细化更新 vs 整树重渲染

框架更新粒度触发方式
Vue 2组件级watcher 触发组件 re-render
React (默认)组件级setState → 整组件 + 子树 re-render
SolidJS表达式级signal getter 追踪 → 仅相关 effect 重跑
Kuiklyattr 块级observable 追踪 → 仅相关 attr 块重跑

渲染指令 · 跨端通用语言

所有 UI 操作 → 6 类核心指令 → 通过 Bridge 下发到各端原生层

指令参数作用
CreateViewviewId, viewType, parentId创建一个原生 View
SetPropviewId, key, value修改 View 的某个属性
AddEventListenerviewId, event给 View 注册事件监听
RemoveEventListenerviewId, event移除事件监听
AddSubviewparentId, childId, index把 child 挂到 parent
RemoveSubviewparentId, childId从 parent 移除 child

实时演示:DSL → 指令

下面的 DSL 转成的指令序列:

View {
    attr { backgroundColor(Color.WHITE); size(100f, 50f) }
    Text { attr { text("Hello") } }
}
--:--:--等待指令…

Bridge:跨语言通信桥

每个平台 Bridge 实现不同,但接口语义统一

平台实现方式开销
AndroidJNI + 直接 JVM 调用极小(普通函数调用)
iOSKotlin/Native ↔ ObjC 互操作极小
HarmonyOSKotlin/Native ↔ ArkTS NAPI较小
Web同语言(Kotlin/JS)
小程序Kotlin/JS + setData中(受小程序架构限制)

「按钮点击 → 屏幕更新」完整链路

1
用户点击原生按钮 View
硬件输入事件被系统捕获
2
Bridge 上抛事件到 Kotlin 层
原生 view.onClick → bridge.onEvent(viewId, "click", payload)
3
Kotlin 找到 click 监听器并执行
监听器内:count++ → observable setter 触发
4
订阅 attr 块加入待重跑队列
不立刻执行,合并到下一帧
5
下一帧:重跑 attr → 新 BuildTree
新值 "Count: 1" 写入节点
6
Diff RenderTree → 生成指令
SetProp(viewId=1, "text", "Count: 1")
7
Bridge 下发指令到原生层
bridge.execute([SetProp(...)])
8
原生层 textView.setText("Count: 1")
下次 vsync 时屏幕显示新画面

★ 整个过程在一帧 16ms 内完成。Android 端因为是 JVM 直接调用,几乎零桥接开销。

跟 Flutter / RN 架构对比

🐦 Flutter(自绘)

Dart → Flutter Engine(C++)→ Skia → OpenGL/Metal → 屏幕

★ 不依赖原生 UI,跨端一致性强

★ 引擎包体大(5MB+)

★ 系统组件需自己模拟

⚡ Kuikly(原生渲染)

Kotlin → KMP 编译 → 跨端核心层 → 渲染指令 → 各端原生 View

★ 复用各平台原生 UI,体验真原生

★ 包体小(300KB-1MB)

★ 系统组件直接用,无需模拟

维度KuiklyFlutterReact Native
开发语言KotlinDartJavaScript
渲染方式原生控件Skia 自绘原生控件
SDK 体积 (Android)~300KB~5MB~3MB
桥接开销极小(同语言/JNI)无(自绘)大(JS Bridge 序列化)
跨端一致性中(保留平台差异)高(自己画)中(受原生影响)
真原生体验⚠️ 模拟
鸿蒙支持✅ 一等公民第三方插件第三方插件
小程序✅ 原生支持