Skip to content

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

学习目标:搞清楚一份 Kotlin 代码是怎么变成 6 端原生产物的;理解 Kuikly「两棵树架构」的设计思想;摸清「渲染指令」是如何从 Kotlin 层流到原生层的;掌握"为什么 Kuikly 比 RN 快、比 Flutter 轻"的本质答案。


7.1 架构鸟瞰图

   ┌────────────────────────────────────────────────────────────────────┐
   │                    Kuikly 整体架构(6 端通用)                       │
   ├────────────────────────────────────────────────────────────────────┤
   │                                                                      │
   │   你的业务代码                                                        │
   │   ─────────────                                                      │
   │   @Page("Home")                                                     │
   │   class HomePage : Pager() { ... }   ← Kotlin commonMain            │
   │              │                                                       │
   │              │ KMP 编译                                              │
   │              ▼                                                       │
   │   ┌──────────────────────────────────────────────────────────┐     │
   │   │  Kuikly 跨端核心层(commonMain)                            │     │
   │   │  ────────────────────────────────────                       │     │
   │   │   - DSL 解析  → BuildTree(原型树)                         │     │
   │   │   - Diff      → RenderTree(渲染树)                        │     │
   │   │   - 测量、布局(Yoga / Flexbox)                            │     │
   │   │   - 响应式调度(observable)                                 │     │
   │   │   - 渲染指令生成(CreateView / SetProp / ...)              │     │
   │   └──────────────────────────────────────────────────────────┘     │
   │              │                                                       │
   │              │ 渲染指令 (JSON-like) 通过 Bridge 下发                 │
   │              ▼                                                       │
   │   ┌─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐  │
   │   │ Android │  iOS    │ HarmonyOS│   Web   │ 小程序  │  macOS  │  │
   │   │ 原生层  │ 原生层  │ 原生层  │ 原生层  │ 原生层  │ 原生层  │  │
   │   │         │         │         │         │         │         │  │
   │   │ View    │ UIView  │ ArkUI   │   DOM   │  WXML   │ NSView  │  │
   │   └─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘  │
   │                                                                      │
   └────────────────────────────────────────────────────────────────────┘

📌 三层心智

  • 业务层:你写的 @Page Kotlin 类
  • 跨端核心层:Kuikly 框架自己(90% 用 Kotlin commonMain 写)
  • 原生渲染层:每个平台一份「轻量的桥接 + 控件适配器」,10% 用平台原生语言

7.2 KMP 编译:一份代码 → 6 端产物

7.2.1 KMP 是什么

KMP(Kotlin Multiplatform) 是 JetBrains 推出的多平台编译能力。同一份 Kotlin 代码可以编译到不同目标:

   ┌─────────────────────────────────────────────────────────┐
   │  KMP 目标平台 → 编译产物                                  │
   ├─────────────────────────────────────────────────────────┤
   │                                                           │
   │   Kotlin/JVM      → .class / .jar / .aar (Android, JVM)  │
   │   Kotlin/Native   → .framework / .dylib (iOS, macOS)     │
   │                   → .so (HarmonyOS, Linux)               │
   │                   → .a (静态库)                          │
   │   Kotlin/JS       → .js (Web, 小程序)                    │
   │   Kotlin/Wasm     → .wasm (实验中)                        │
   │                                                           │
   └─────────────────────────────────────────────────────────┘

7.2.2 「commonMain + actual」的源码集结构

KMP 工程的源码组织方式:

shared/
└── src/
    ├── commonMain/         ← 跨端共享代码(90%)
    │   └── kotlin/
    │       └── ...

    ├── androidMain/        ← Android 平台特有(10%)
    ├── iosMain/            ← iOS 平台特有
    ├── ohosArm64Main/      ← HarmonyOS 平台特有
    ├── jsMain/             ← Web 平台特有

    └── commonTest/         ← 跨端测试

7.2.3 expect / actual 机制:写跨端 API 的标准范式

如果某个 API 需要平台差异化实现,用 expect 在 commonMain 声明,在每个 platformMain 里用 actual 实现:

kotlin
// commonMain
expect fun currentPlatform(): String

// androidMain
actual fun currentPlatform(): String = "Android ${android.os.Build.VERSION.RELEASE}"

// iosMain
actual fun currentPlatform(): String = "iOS ${UIDevice.currentDevice.systemVersion}"

// jsMain
actual fun currentPlatform(): String = "Web ${js("navigator.userAgent")}"

调用方写法跟普通函数没区别:

kotlin
// commonMain
val platform = currentPlatform()
println("Hello from $platform")

7.2.4 编译产物对照

平台KMP 目标编译产物集成方式
Androidjvm.aarGradle 依赖
iOSnative (arm64/x64).frameworkCocoaPods / SPM
HarmonyOSnative (arm64).sohvigor
Webjs (IR).js (UMD)npm / CDN
小程序js.js小程序构建
macOSnative (arm64/x64).frameworkSPM

7.3 两棵树:BuildTree + RenderTree

7.3.1 为啥要"两棵树"?

回顾第 1 章:传统跨端框架(RN / Flutter)走「虚拟 DOM 三棵树」:

   业务 DSL → Virtual Tree → Diff → Element Tree → RenderObject Tree → 平台原生
              (3 棵 + 2 次 diff,开销不小)

Kuikly 优化为「两棵树」:

   业务 DSL ─→ BuildTree ─diff─→ RenderTree ─→ 原生指令
                  ↑                  ↑
               Kotlin 层(90%)   Kotlin 层算,再桥到 Native(10%)

7.3.2 BuildTree(原型树)

执行 ViewBuilder 闭包后,得到的"组件原型描述"。它包含 所有节点,包括纯逻辑节点(如 vfor、vif 这种"指令节点")。

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

   ↓↓↓

   BuildTree:
     View
       ├── Text "A"
       ├── ConditionNode (showB) ── Text "B"   ← 逻辑节点
       └── ForNode (list) ── [Text x N]        ← 逻辑节点

7.3.3 RenderTree(渲染树)

从 BuildTree 中剔除所有逻辑节点,只留下"会真正变成原生控件"的节点:

   RenderTree:
     View
       ├── Text "A"
       ├── Text "B"     (showB=true 时)
       ├── Text item1
       ├── Text item2
       └── Text item3

RenderTree 跟原生控件1:1 对应。Kuikly 的 diff 算法只在这棵树上做,更轻量。

7.3.4 Diff 算法:精细化更新

当 observable 字段变化 → 重跑相关 attr 块 → 生成新的 BuildTree → 与旧的 BuildTree 做 diff → 把差异编译成「渲染指令」。

精细化指什么?看下面对比:

   场景:count 从 0 → 1,影响一个 Text 的 text 属性

   ┌───────────────────────────────────────────────────────┐
   │  RN:重渲染整个组件树 + 比较 props                       │
   │   ↓                                                    │
   │   再决定:Text 的 props.text 变了 → 调原生 setText      │
   ├───────────────────────────────────────────────────────┤
   │  Kuikly:直接知道「count 字段被这个 attr 块用了」         │
   │   ↓                                                    │
   │   只重跑这个 attr 块 → setProp("text", "1") 指令         │
   │                                                        │
   │   ★ O(1) 精细化更新,不重建任何节点                     │
   └───────────────────────────────────────────────────────┘

7.4 渲染指令:跨端通信的"通用语言"

7.4.1 指令格式

Kuikly 把 UI 操作抽象成 6 类核心指令,序列化后通过 Bridge 发到原生层:

   ┌─────────────────────────────────────────────────────┐
   │  Kuikly 渲染指令清单                                  │
   ├─────────────────────────────────────────────────────┤
   │                                                       │
   │  1. CreateView(viewId, type, parentId)               │
   │     - 创建一个原生 View                                │
   │                                                       │
   │  2. SetProp(viewId, key, value)                       │
   │     - 修改某个 View 的属性                              │
   │                                                       │
   │  3. AddEventListener(viewId, event)                   │
   │     - 给 View 注册事件监听                             │
   │                                                       │
   │  4. RemoveEventListener(viewId, event)                │
   │                                                       │
   │  5. AddSubview(parentId, childId, index)              │
   │     - 把 child 挂到 parent 的指定位置                  │
   │                                                       │
   │  6. RemoveSubview(parentId, childId)                  │
   │     - 把 child 从 parent 移除                          │
   │                                                       │
   └─────────────────────────────────────────────────────┘

7.4.2 一次按钮点击的完整流程

假设业务代码:

kotlin
private var count by observable(0)

Text { attr { text("Count: $count") } }
Button {
    event { click { ctx.count++ } }
    Text { attr { text("+1") } }
}

用户点击按钮 → 完整链路:

   ① 用户点击原生按钮 View


   ② 原生层捕获 click 事件 → 通过 Bridge 上抛到 Kotlin 层


   ③ Kotlin 层找到 viewId=2 的 click 监听器


   ④ 执行 click { ctx.count++ }


   ⑤ count 字段 setter 触发 → 加入待重跑队列


   ⑥ 下一帧:重跑读了 count 的 attr 块


   ⑦ 拿到新的 text = "Count: 1"


   ⑧ Diff: 跟旧值 "Count: 0" 不同 → 生成指令
      │   SetProp(viewId=1, key="text", value="Count: 1")

   ⑨ 通过 Bridge 把指令发到原生层


   ⑩ 原生层执行:textView1.setText("Count: 1")


   屏幕显示 "Count: 1"

整个过程没有任何序列化 / 反序列化的开销(Kotlin/JVM 直接函数调用),没有 JS 桥


7.5 Bridge:跨语言通信桥

7.5.1 各端 Bridge 的不同实现

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

平台Bridge 实现性能
AndroidJNI + 直接 JVM 调用(无桥)极快(普通函数调用)
iOSKotlin/Native 与 OC/Swift 互操作极快(直接 ObjC 消息)
HarmonyOSKotlin/Native 与 ArkTS 桥较快(NAPI 调用)
WebKotlin/JS 与 DOM 直调快(同语言)
小程序Kotlin/JS + 小程序 setData中(受小程序架构限制)

7.5.2 Bridge 的"两个方向"

   ┌────────────────────────────────────────────────────┐
   │   Kotlin 层                                         │
   │      ↑                          │                   │
   │      │ 上行:事件回调            │ 下行:渲染指令     │
   │      │ click / scroll / ...     │ CreateView ...    │
   │      │                          ↓                   │
   │   原生层(Android View / iOS UIView / ...)         │
   └────────────────────────────────────────────────────┘

7.5.3 跟 RN Bridge 的本质差异

   ┌─────────────────────────────────────────────────────────────┐
   │  RN Bridge(旧架构)                                          │
   ├─────────────────────────────────────────────────────────────┤
   │   JS 引擎 ─── 序列化 JSON ───→ 跨线程消息队列 ───→ 原生        │
   │            ↑ 序列化开销大     ↑ 异步消息有延迟   ↑ 反序列化开销 │
   │   每次调用都"一来一回",频繁调用时累计延迟显著                  │
   ├─────────────────────────────────────────────────────────────┤
   │  Kuikly Bridge(Android)                                    │
   ├─────────────────────────────────────────────────────────────┤
   │   Kotlin/JVM 直接函数调用 ───→ Android View 操作              │
   │            ↑ 跟普通 Kotlin 调用一样                            │
   │   无序列化、无消息队列、无线程跳转 → 极致性能                   │
   └─────────────────────────────────────────────────────────────┘

📌 关键洞察:Android 上 Kuikly 编出来的 .aar 就是 JVM 字节码,跟你写的 Kotlin 代码本质相同。跑起来 KuiklyView.callMethod(...) 就是 targetObject.callMethod(...)没有"桥"


7.6 KSP(Kotlin Symbol Processing):编译期魔法

7.6.1 KSP 是什么

KSP 是 Google 出品的 Kotlin 注解处理器,在编译期扫描你的源码,生成额外的 Kotlin 代码。Kuikly 用它做了几件大事:

   ┌─────────────────────────────────────────────────────┐
   │  Kuikly KSP 在编译期做了什么                          │
   ├─────────────────────────────────────────────────────┤
   │                                                       │
   │  1. 扫描所有 @Page("xxx") 注解                        │
   │     → 生成 PagerRegistry:name → factory 映射         │
   │                                                       │
   │  2. 扫描所有 @Component 注解                          │
   │     → 生成组件工厂                                     │
   │                                                       │
   │  3. 生成 KuiklyCoreEntry.triggerRegisterPages()       │
   │     → 一行代码注册全部页面                             │
   │                                                       │
   └─────────────────────────────────────────────────────┘

7.6.2 生成代码长啥样

你写的:

kotlin
@Page("HomePage")
class HomePage : Pager() { ... }

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

KSP 在编译期生成:

kotlin
// 自动生成,你看不到,但确实存在
object KuiklyCoreEntry {
    fun triggerRegisterPages() {
        PagerRegistry.register("HomePage") { HomePage() }
        PagerRegistry.register("DetailPage") { DetailPage() }
    }
}

宿主在 MainActivity.onCreate 里调一次 triggerRegisterPages(),所有页面就登记好了。

7.6.3 为啥用 KSP 而不是反射?

维度KSP(编译期生成)反射(运行时扫描)
启动速度快(直接函数调用)慢(要扫包)
包体大(要带 Reflect 库)
iOS 兼容✅(Kotlin/Native 不支持完整反射)
错误时机编译期(IDE 高亮)运行时(线上才暴露)

📌 结论:KSP 是 KMP 项目的"标配",特别是 iOS / 鸿蒙这种 Kotlin/Native 平台,反射受限。


7.7 跟 Flutter 架构对比

   ┌──────────────────────────────────────────────────────────────┐
   │   Flutter 架构                                                 │
   ├──────────────────────────────────────────────────────────────┤
   │                                                                │
   │   你的 Dart 代码                                                │
   │      ↓                                                         │
   │   Flutter Engine(C++ 核心)                                   │
   │      ↓                                                         │
   │   Skia 自绘引擎(自己画所有像素)                                │
   │      ↓                                                         │
   │   各平台 Surface(OpenGL / Vulkan / Metal)                    │
   │      ↓                                                         │
   │   屏幕                                                          │
   │                                                                │
   │   ★ 不依赖原生 UI 系统                                          │
   │   ★ 跨端渲染一致性强                                             │
   │   ★ 引擎包体大(5MB+)                                           │
   │   ★ 原生组件需自己模拟(系统弹窗、键盘行为有差异)                 │
   └──────────────────────────────────────────────────────────────┘

   ┌──────────────────────────────────────────────────────────────┐
   │   Kuikly 架构                                                  │
   ├──────────────────────────────────────────────────────────────┤
   │                                                                │
   │   你的 Kotlin 代码                                              │
   │      ↓ KMP 编译                                                │
   │   Kuikly 跨端核心层(Kotlin commonMain)                        │
   │      ↓ 渲染指令                                                 │
   │   各端原生渲染层(Android View / iOS UIView / ArkUI / ...)    │
   │      ↓                                                         │
   │   屏幕                                                          │
   │                                                                │
   │   ★ 复用各平台原生 UI 系统                                       │
   │   ★ 跨端有"系统差异"但用户感觉自然                                 │
   │   ★ 包体小(300KB-1MB)                                         │
   │   ★ 系统组件直接用,无需模拟                                       │
   └──────────────────────────────────────────────────────────────┘

7.8 一帧的"完整生命周期"

把所有概念串起来:

   一帧时间内(16.67ms)发生了什么?
   ────────────────────────────────────────

   t=0     用户操作(点击 / 滚动)
   t=1     原生层捕获 → Bridge 上抛
   t=2     Kotlin 事件回调执行
   t=3     observable 字段变化 → setter 触发
   t=4     待重跑队列被加入相关 attr 块
   t=5     ── 进入下一帧 ──
   t=6     批量重跑 attr 块 → 生成新 BuildTree 节点
   t=7     Diff → 生成渲染指令列表
   t=8     指令通过 Bridge 下发
   t=9     原生层执行:setText / setBackground / ...
   t=10    系统调度下一次 vsync 信号
   t=16    屏幕刷新,用户看到新画面

📌 整个过程在单帧时间内完成,60fps 跑得稳。


7.9 章末小结

                  ★ 第 7 章架构知识图谱 ★

       ┌────────────────────┼─────────────────────┐
       │                    │                     │
   ┌───▼─────┐       ┌──────▼──────┐         ┌───▼──────┐
   │ KMP 编译│       │ 两棵树架构  │         │ Bridge   │
   ├─────────┤       ├─────────────┤         ├──────────┤
   │ common  │       │ BuildTree   │         │ Android: │
   │ actual  │       │ RenderTree  │         │   JNI    │
   │ 6 端产物│       │ O(1) Diff   │         │ iOS:     │
   │ KSP     │       │ 精细化更新   │         │   ObjC 互操│
   └─────────┘       └─────────────┘         │ Web/小程序│
                            │                 │   同语言  │
                            ▼                 └──────────┘
                   渲染指令清单
                   CreateView / SetProp / AddSub / ...

🎤 7.10 章末面试题(10 道高频题)

Q1. KMP 是什么?跟 Kuikly 是什么关系?

KMP(Kotlin Multiplatform) 是 JetBrains 推出的 Kotlin 多平台编译能力,能把同一份 Kotlin 代码编译到 Android / iOS / Web / 桌面 / 鸿蒙等多平台。

Kuikly 基于 KMP 构建:业务代码用 KMP 编译共享,UI 用 Kuikly 的 DSL 写。可以理解为「KMP 解决了"业务逻辑跨端",Kuikly 解决了"UI 跨端"」。

Q2. 为什么 commonMain 不能直接调 Android API?

:因为 commonMain 要被编译到所有平台。Android API(android.content.Context 等)只在 Android 平台存在,iOS / 鸿蒙编译时找不到符号会报错。

如果业务确实需要平台 API,KMP 提供 expect / actual 机制:commonMain 写 expect,各 platformMain 写 actual 实现。

Q3. Kuikly 的「两棵树」具体是哪两棵?

  • BuildTree(原型树):DSL 闭包执行后生成的所有节点,包含逻辑节点(vif、vfor 等)
  • RenderTree(渲染树):从 BuildTree 剔除逻辑节点,只留下「与原生控件 1:1 对应」的节点

Diff 在 RenderTree 上做,避免了"逻辑节点"参与 diff 的浪费。

Q4. Kuikly 的 Diff 跟 React/Vue 有什么本质不同?

  • React/Vue:从根节点开始遍历整棵树,对比 props 决定是否更新
  • Kuikly:通过响应式追踪知道「哪个 attr 块依赖了变化的字段」,只重跑那个 attr 块,不遍历整棵树

后者是 O(1) 精细化更新(更新次数和数据变化次数线性相关,与组件总数无关)。

Q5. Kuikly 的渲染指令有哪几类?

:6 大核心指令:

  1. CreateView(viewId, type, parentId)
  2. SetProp(viewId, key, value)
  3. AddEventListener(viewId, event)
  4. RemoveEventListener(viewId, event)
  5. AddSubview(parentId, childId, index)
  6. RemoveSubview(parentId, childId)

所有 UI 操作都能用这 6 类组合表达,指令格式跨端统一,每个原生层只需要实现这 6 个 handler。

Q6. Kuikly 的 Bridge 跟 RN Bridge 有什么本质区别?

  • RN Bridge:JS 引擎 ↔ 原生,需要序列化 JSON + 跨线程消息,频繁调用时累积延迟显著
  • Kuikly Bridge(Android):Kotlin/JVM 直接 JNI 调用,没有序列化、没有跨线程,跟普通 Kotlin 函数调用一样
  • Kuikly Bridge(iOS):Kotlin/Native 直接调 OC,几乎零开销

性能上,Kuikly Bridge ≈ 原生调用,RN Bridge 慢 1-2 个数量级。

Q7. KSP 在 Kuikly 里干了什么?为什么不用反射?

:KSP 在编译期:

  • 扫描所有 @Page("xxx") → 生成路由注册代码
  • 扫描所有 @Component → 生成组件工厂
  • 生成 KuiklyCoreEntry.triggerRegisterPages() 入口

不用反射的原因:

  1. iOS / 鸿蒙是 Kotlin/Native 平台,反射受限
  2. 反射有运行时开销 + 包体增大
  3. KSP 生成的代码错误能在编译期暴露(IDE 红线),反射要运行时才能发现问题

Q8. Kuikly 跟 Flutter 在架构上最大的区别是什么?

渲染层

  • Flutter:自带 Skia 渲染引擎,自己画所有像素,不依赖原生 UI 系统 → 跨端一致 + 包体大 + 系统组件需模拟
  • Kuikly:复用各端原生 UI 系统(Android View / iOS UIView / ArkUI 等),只做"指令翻译" → 包体小 + 体验真原生 + 跨端有自然差异

哲学不同:Flutter 是"自己画一个 UI",Kuikly 是"用平台已有的 UI"。

Q9. 一次「按钮点击 → 屏幕更新」的完整链路是什么?

:10 步:

  1. 用户点击原生按钮 View
  2. 原生层捕获 click 事件
  3. Bridge 上抛事件到 Kotlin 层
  4. Kotlin 层找到 viewId 对应的 click 监听器并执行
  5. 监听器修改 observable 字段
  6. observable setter 触发,把订阅 attr 块加入待重跑队列
  7. 下一帧调度时,批量执行待重跑队列
  8. 重跑产生新的 BuildTree → diff → 渲染指令
  9. 指令通过 Bridge 下发到原生层
  10. 原生层执行 setText / setBackground 等 → 屏幕刷新

Q10. 为什么说 Kuikly Android 端"几乎没有桥"?

:Kuikly Android 端的 shared KMP 模块编译出的 .aar 就是 JVM 字节码,跟你写的普通 Kotlin Android 代码本质相同。

调用 Android View 时是普通的 JVM 方法调用,跟你在 MainActivity 里写 view.setText("...") 一模一样。没有序列化、没有消息队列、没有线程切换

这跟 RN(JS ↔ Native 跨语言桥)有本质区别。这也是 Kuikly Android 端能做到「真·原生性能」的根本原因。


下一站 → 第 8 章 · 平台通信 Module / 调原生 API / expect-actual →

🎬 可视化演示

演示加载缓慢或样式异常?点此在新标签页打开 ↗

💻 示例代码

kotlin
/**
 * 第 7 章配套代码 · expect / actual 跨端 API 范式
 *
 * 演示如何用 KMP 的 expect/actual 机制,写一个跨端的"获取平台信息" API。
 *
 * 这是 KMP 工程组织代码的"标准范式",第 8 章会进一步演化成 Module 概念。
 */

// ═══════════════════════════════════════════════════════════════════
// commonMain:公共声明(业务代码只调这一份)
// 文件位置:shared/src/commonMain/kotlin/com/example/kuikly/platform/PlatformInfo.kt
// ═══════════════════════════════════════════════════════════════════
package com.example.kuikly.platform

/**
 * 当前平台名称,如 "Android 14" / "iOS 17.0" / "HarmonyOS 4.0"
 */
expect fun currentPlatform(): String

/**
 * 屏幕物理像素宽 × 高
 */
expect fun screenSize(): Pair<Int, Int>

/**
 * 设备唯一 ID(演示用,真实场景注意隐私合规)
 */
expect fun deviceId(): String


// ═══════════════════════════════════════════════════════════════════
// androidMain:Android 平台实现
// 文件位置:shared/src/androidMain/kotlin/com/example/kuikly/platform/PlatformInfo.kt
// ═══════════════════════════════════════════════════════════════════
/*
package com.example.kuikly.platform

import android.os.Build
import android.provider.Settings
import com.example.kuikly.app.KuiklyApplication

actual fun currentPlatform(): String =
    "Android ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})"

actual fun screenSize(): Pair<Int, Int> {
    val ctx = KuiklyApplication.context
    val dm = ctx.resources.displayMetrics
    return dm.widthPixels to dm.heightPixels
}

actual fun deviceId(): String {
    val ctx = KuiklyApplication.context
    return Settings.Secure.getString(ctx.contentResolver, Settings.Secure.ANDROID_ID)
}
*/


// ═══════════════════════════════════════════════════════════════════
// iosMain:iOS 平台实现(Kotlin/Native)
// 文件位置:shared/src/iosMain/kotlin/com/example/kuikly/platform/PlatformInfo.kt
// ═══════════════════════════════════════════════════════════════════
/*
package com.example.kuikly.platform

import platform.UIKit.UIDevice
import platform.UIKit.UIScreen

actual fun currentPlatform(): String {
    val dev = UIDevice.currentDevice
    return "iOS ${dev.systemVersion}"
}

actual fun screenSize(): Pair<Int, Int> {
    val bounds = UIScreen.mainScreen.bounds
    return bounds.useContents { size.width.toInt() to size.height.toInt() }
}

actual fun deviceId(): String =
    UIDevice.currentDevice.identifierForVendor?.UUIDString ?: "unknown"
*/


// ═══════════════════════════════════════════════════════════════════
// jsMain:Web / 小程序实现
// 文件位置:shared/src/jsMain/kotlin/com/example/kuikly/platform/PlatformInfo.kt
// ═══════════════════════════════════════════════════════════════════
/*
package com.example.kuikly.platform

import kotlinx.browser.window

actual fun currentPlatform(): String =
    "Web ${window.navigator.userAgent}"

actual fun screenSize(): Pair<Int, Int> =
    window.innerWidth to window.innerHeight

actual fun deviceId(): String =
    window.localStorage.getItem("device_id")
        ?: ("web_" + (Math.random() * 1e10).toLong()).also {
            window.localStorage.setItem("device_id", it)
        }
*/


/* ─────────────────────────────────────────────────────────
 * 业务代码(commonMain)使用方式:
 *
 *   class HomePage : Pager() {
 *       override fun body() = {
 *           Text { attr { text("当前平台: ${currentPlatform()}") } }
 *           Text { attr { text("屏幕: ${screenSize()}") } }
 *           Text { attr { text("设备: ${deviceId()}") } }
 *       }
 *   }
 *
 * Android 上看到:"Android 14 (SDK 34)"
 * iOS 上看到:"iOS 17.0"
 * Web 上看到:"Web Mozilla/5.0..."
 *
 * ★ 一份 commonMain 业务代码,3 端结果都对 —— 这就是 expect/actual 的威力
 * ─────────────────────────────────────────────────────── */
kotlin
/**
 * 第 7 章配套代码 · 渲染指令模型(教学版伪代码)
 *
 * 这是 Kuikly 真实指令的简化版,帮助理解"DSL → 指令 → 原生"的流程。
 * 真实代码在 com.tencent.kuikly.core.render.* 包下。
 *
 * 不要直接编译进生产工程,仅用于学习参考。
 */

package com.example.kuikly.learn.render

// ═══════════════════════════════════════════════════════════════════
// 1. 指令模型(6 大核心指令)
// ═══════════════════════════════════════════════════════════════════
sealed class RenderCommand {

    /** 创建一个原生 View */
    data class CreateView(
        val viewId: Int,
        val viewType: String,           // "View" / "Text" / "Image" / ...
        val parentId: Int? = null,
    ) : RenderCommand()

    /** 设置 View 的某个属性 */
    data class SetProp(
        val viewId: Int,
        val key: String,                // "text" / "backgroundColor" / "width" / ...
        val value: Any?,
    ) : RenderCommand()

    /** 注册事件监听 */
    data class AddEventListener(
        val viewId: Int,
        val event: String,              // "click" / "scroll" / ...
    ) : RenderCommand()

    /** 移除事件监听 */
    data class RemoveEventListener(
        val viewId: Int,
        val event: String,
    ) : RenderCommand()

    /** 把 child 挂到 parent 的指定位置 */
    data class AddSubview(
        val parentId: Int,
        val childId: Int,
        val index: Int = -1,            // -1 = 末尾
    ) : RenderCommand()

    /** 从 parent 移除 child */
    data class RemoveSubview(
        val parentId: Int,
        val childId: Int,
    ) : RenderCommand()
}

// ═══════════════════════════════════════════════════════════════════
// 2. 跨端 Bridge 接口(每个平台实现一份)
// ═══════════════════════════════════════════════════════════════════
interface RenderBridge {

    /** 下行:Kotlin 层 → 原生层下发指令 */
    fun execute(commands: List<RenderCommand>)

    /** 上行:原生层 → Kotlin 层抛事件回调 */
    fun onEvent(viewId: Int, event: String, payload: Map<String, Any?>)
}

// ═══════════════════════════════════════════════════════════════════
// 3. Android 端 Bridge 实现(伪代码示意)
// ═══════════════════════════════════════════════════════════════════
/*
class AndroidRenderBridge(private val rootViewGroup: ViewGroup) : RenderBridge {
    private val viewMap = mutableMapOf<Int, View>()

    override fun execute(commands: List<RenderCommand>) {
        commands.forEach { cmd -> executeOne(cmd) }
    }

    private fun executeOne(cmd: RenderCommand) = when (cmd) {
        is RenderCommand.CreateView -> {
            val view = when (cmd.viewType) {
                "View" -> FrameLayout(rootViewGroup.context)
                "Text" -> TextView(rootViewGroup.context)
                "Image" -> ImageView(rootViewGroup.context)
                else -> error("Unknown viewType: ${cmd.viewType}")
            }
            viewMap[cmd.viewId] = view
        }

        is RenderCommand.SetProp -> {
            val view = viewMap[cmd.viewId] ?: return
            when (cmd.key) {
                "text" -> (view as? TextView)?.text = cmd.value as String
                "backgroundColor" -> view.setBackgroundColor(cmd.value as Int)
                "width" -> view.layoutParams = view.layoutParams.apply { width = cmd.value as Int }
                "height" -> view.layoutParams = view.layoutParams.apply { height = cmd.value as Int }
                // ... 几十种属性
            }
        }

        is RenderCommand.AddEventListener -> {
            val view = viewMap[cmd.viewId] ?: return
            when (cmd.event) {
                "click" -> view.setOnClickListener {
                    onEvent(cmd.viewId, "click", mapOf("x" to it.x, "y" to it.y))
                }
            }
        }

        is RenderCommand.AddSubview -> {
            val parent = viewMap[cmd.parentId] as? ViewGroup ?: return
            val child = viewMap[cmd.childId] ?: return
            if (cmd.index >= 0) parent.addView(child, cmd.index) else parent.addView(child)
        }

        // ... 其他指令
        else -> { }
    }

    override fun onEvent(viewId: Int, event: String, payload: Map<String, Any?>) {
        // 通过 JNI 上抛到 Kotlin 层的事件分发器
        EventDispatcher.dispatch(viewId, event, payload)
    }
}
*/

// ═══════════════════════════════════════════════════════════════════
// 4. 业务侧使用示例(教学伪代码)
// ═══════════════════════════════════════════════════════════════════
/*
// 真实的 Kuikly DSL:
View {
    attr {
        backgroundColor(Color.WHITE)
        size(100f, 50f)
    }
    Text { attr { text("Hello") } }
}

// 编译期 + 运行期最终生成的指令序列:
[
    CreateView(viewId=1, viewType="View"),
    SetProp(viewId=1, key="backgroundColor", value=0xFFFFFFFF),
    SetProp(viewId=1, key="width", value=100),
    SetProp(viewId=1, key="height", value=50),
    CreateView(viewId=2, viewType="Text", parentId=1),
    SetProp(viewId=2, key="text", value="Hello"),
    AddSubview(parentId=1, childId=2),
]

// 这串指令通过 Bridge 下发到 Android 原生层,最终:
//   FrameLayout (root, 100x50, 白底)
//     └── TextView "Hello"
*/

PlatformInfo.kt ↗ · RenderCommands.kt ↗