当前位置: 首页 > news >正文

Kuikly 小白拆解系列 · 第1篇|两棵树直调(Kotlin 构建与原生承载)

Kuikly 小白拆解系列 · 第1篇|两棵树直调(Kotlin 构建与原生承载)

阅读地图(由浅入深)

  • 目标:帮助 Android 团队快速理解 Kuikly 的跨平台渲染原理,并给出在 Android 侧的工程化落地路径与实践要点。
  • 读者:面向有原生 Android 经验的开发者、技术负责人,以及评估跨平台方案的架构师。
  • 结构:先上手,再理解渲染模型,最后以源码佐证与工程落地。
  • 结论先行:Kuikly 属于“跨平台原生渲染(NA 渲染)”,不直接在跨平台层调用 Skia,而是通过原生组件和 Canvas 路径绘制;跨端复杂逻辑统一在 Kotlin 层,两棵树直调,平台层专注绘制与属性映射。

基础使用:三端最小案例(先上手再深入)

  • 目标:用一个“Hello Kuikly”页面,演示 Android/iOS/鸿蒙 的最小接入与启动,建立跨端一致的心智模型。
  • 说明:以下代码均为概念示例(示意),与团队工程的命名可能略有不同,但路径与思路一致。

通用页面(Kotlin DSL,跨平台层)

@Page("hello")
class HelloPage : Pager(){override fun body(): ViewBuilder = {attr { allCenter(); backgroundColor(Color.WHITE) }Text { attr { fontSize(20f); color(Color.GREEN); text("Hello Kuikly") } }}
}

DSL 术语与命名科普(attr 等该怎么理解)

  • attr(属性块):在 Kuikly DSL 中,每个组件都可以通过 attr { ... } 声明该组件的属性。作用域是“当前组件”,用于统一设置布局、样式、无障碍等属性。

    • 布局属性示例:size(100f, 50f), margin(8f), flexDirectionRow(), allCenter()(水平垂直居中)。
    • 样式属性示例:backgroundColor(Color.WHITE), color(Color.GREEN), fontSize(20f), border(1f, Color.GRAY)
    • 事件与可访问性在 Kuikly 中通常通过 event { ... }accessibility { ... } 分块;但“属性”与“事件”在桥接过程中都会被统一映射到平台控件。
    • 参考拓展:docs/DevGuide/attr.mddocs/API/components/basic-attr-event.md 提供了完整属性清单与示例。
  • 命名与含义(Kuikly 自有术语)

    • Page / Pager:页面声明与承载的基类;@Page("hello") 注解用于构建期注册页面,Pagerbody(): ViewBuilder 返回 DSL 构建树。
    • ViewBuilder:DSL 构建器类型(一个函数块),用于以声明式方式组合组件与其属性。
    • View / Text / Image:Kuikly 的声明式组件名,对应平台原生控件或由 Kotlin 层组合实现的高阶组件。
    • Color:Kuikly 的颜色类型,跨平台统一(可由 Compose Color 转换至 Kuikly Color)。
    • allCenter:布局快捷方法(组合语义),实现内容在父容器中水平与垂直居中;等价于把主轴和交叉轴的对齐都设为居中(Flex 布局)。
    • NativeBridge / Adapter:桥接与适配器层的通用命名;前者负责把 Kotlin 层的“通用方法调用”送达平台渲染器,后者在平台层完成属性映射(如颜色、字体、图片、日志)。
  • 属性到平台的映射(工作机理)

    • Kotlin 层:attr { ... } 会把属性聚合到组件的属性集合中;渲染时通过桥接调用把“属性键值”发送到平台。
    • 桥接统一:BridgeManager 通过 NativeBridge.toNative(methodId, ...args) 把属性设置的通用调用传到各端实现。
    • 平台侧:Android/iOS/鸿蒙的渲染器接收属性,并在原生控件上执行对应的 setXxx 或绘制逻辑(Android 的 View/Canvas,iOS 的 UIView,OHOS 的 ArkUI)。

示例(attr 的作用域与映射)

// 1) 容器属性:当前 View 容器设置布局与样式
View {attr {size(200f, 100f)backgroundColor(Color.WHITE)allCenter() // 让子元素居中}// 2) 子组件属性:只影响当前 Text,不影响父容器Text {attr {text("Hello Kuikly")fontSize(20f)color(Color.GREEN)}}
}

Android 基础使用(原生组件/容器启动)

  • 依赖:确保引入 Kuikly 跨平台产物与 Android 渲染器(core + core-render-android),并将 Gradle JDK 切换到 JDK17(Android Studio ≥ 2024.2.1)。
  • 启动容器:
// 在任意位置(Activity/Fragment)启动 Kuikly 页面(示意)
KuiklyRenderActivity.start(context, "hello", JSONObject())// 若你已在布局中预留容器(ViewGroup),也可通过适配器将 NativeOps 映射到原生 View(示意)
// val container: ViewGroup = findViewById(R.id.container)
// AndroidNativeOps(context, container).also { ops -> HelloPage().render(ops) }

iOS 基础使用(UIKit/SwiftUI 容器)

  • 依赖:集成 Kuikly iOS 渲染器(SPM/Pod),使用 Swift 工程。
  • 启动容器(示意):
// UIKit 示例:推入 Kuikly 页面(示意)
let vc = KuiklyRenderViewController(pageName: "hello", params: [:])
navigationController?.pushViewController(vc, animated: true)// SwiftUI 容器:用 UIViewControllerRepresentable 包装(示意)
struct KuiklyPageView: UIViewControllerRepresentable {func makeUIViewController(context: Context) -> UIViewController {KuiklyRenderViewController(pageName: "hello", params: [:])}func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

鸿蒙(OpenHarmony/ArkTS)基础使用(Stage 模型)

  • 依赖:引入 Kuikly OHOS 渲染器模块,Stage 模型工程(hvigor)。
  • 启动容器(示意):
// UIAbility:初始化 Kuikly 并通过业务路由打开 Kuikly 页面(示意)
import router from '@ohos.router'
import window from '@ohos.window'
import { KuiklyRenderAdapterManager } from '@kuikly-open/render'export default class EntryAbility extends UIAbility {onWindowStageCreate(windowStage: window.WindowStage): void {// 初始化日志、路由等适配器(详见 docs/QuickStart/harmony.md)KuiklyRenderAdapterManager.krRouterAdapter = new AppKRRouterAdapter()// 以路由方式打开 Kuikly 页面(hello)router.pushUrl({url: 'pages/Index',params: { pageName: 'hello', pageData: {} }})}
}

结合案例展开(从“hello”页面理解渲染路径)

  • Android:原生组件或 Canvas 绘制,框架底层映射到 Skia;属性由跨平台层直调映射到原生控件。
  • iOS:UIKit/CoreAnimation 管线绘制,容器承载 Kuikly 页面;属性与布局由跨平台层统一生成。
  • 鸿蒙:ArkUI/AGP 渲染管线;同样通过容器承载与属性映射实现一致渲染。
  • 后续章节深入解释“两棵树直调”、“原生渲染与 Skia/Canvas 的关系”、以及“属性映射的工程要点”;你可以用上述“hello”页面对照理解与验证。

渲染模型速览:原生渲染 vs 自绘渲染(定义与取舍)

  • 原生渲染(Native Rendering):沿用平台控件与渲染管线(Android 的 View/RecyclerView、iOS 的 UIView),以“属性 + 组合”构建 UI。
    • 优点:生态成熟、冷启与内存更友好、混合原生能力强。
    • 典型代码:
// 原生组件渲染(Android)
val tv = TextView(context).apply {text = "Hello"setTextColor(Color.GREEN)
}
container.addView(tv)
  • 自绘渲染(Self-Draw Rendering):维护自有画布与合成树,直接驱动底层 2D 引擎(如 Skia)。
    • 优点:跨端 UI 一致性强。
    • 代价:引擎初始化与内存成本更高,混合原生控件存在层级与同步问题。
    • 典型代码:
// Canvas 自绘(Android)
class BadgeView(context: Context): View(context){private val p = Paint(Paint.ANTI_ALIAS_FLAG).apply{ color = Color.RED; textSize = 28f }override fun onDraw(c: Canvas){c.drawCircle(width/2f, height/2f, 24f, p)c.drawText("Hot", 16f, 36f, p)}
}

Kuikly 到底是什么渲染(结论与心智模型)

  • 结论:Kuikly 属于“跨平台原生渲染(NA 渲染)”。跨平台层(Kotlin)统一建树、测量、布局与状态;平台层沿用原生组件/Canvas 绘制。
  • 心智模型:两棵树直调——跨平台层维护唯一 UI 原型树;通过通用原子接口直调平台原生控件形成平台侧 Native 树;避免多层 Diff 与跨语言序列化开销。
  • 对比自绘:Kuikly 不维护独立的画布与合成树,不在跨平台层直接调用 Skia;最大化复用平台渲染管线与生态。

源码速证(三端)

  • 容器与页面打开:androidApp/src/.../KuiklyRenderActivity.kt
// onCreate中核心流程(源码节选)
// 文件:androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyRenderActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)contextCodeHandler = ContextCodeHandler(this, pageName)kuiklyRenderViewDelegator = contextCodeHandler.initContextHandler()setContentView(R.layout.activity_hr)setupAdapterManager()hrContainerView = findViewById(R.id.hr_container)// 触发Kuikly页面实例化contextCodeHandler.openPage(hrContainerView, pageName, createPageData())
}

// iOS:SwiftUI 容器包装 UIKit 控制器(iosApp/iosApp/KuiklyRenderViewPage.swift)

// 以 SwiftUI 承载 Kuikly 页面(源码节选)
struct KuiklyRenderViewPage : UIViewControllerRepresentable {var pageName: String; var data: Dictionary<String, Any>func makeUIViewController(context: Context) -> UINavigationController {let hrVC = KuiklyRenderViewController(pageName: pageName, pageData: data)return UINavigationController(rootViewController: hrVC)}func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}

// 鸿蒙:路由适配器打开页面(docs/QuickStart/harmony.md → AppKRRouterAdapter.ets)

// 通过路由参数传递 Kuikly 的 pageName 与 pageData(源码节选)
export class AppKRRouterAdapter implements IKRRouterAdapter {openPage(context: common.UIAbilityContext, pageName: string, pageData: KRRecord): void {router.pushUrl({ url: 'pages/Index', params: { pageName, pageData } })}closePage(context: common.UIAbilityContext): void { router.back() }
}
  • 页面注解与路由注册:core-annotations/src/.../Page.kt
// 文件:core-annotations/src/commonMain/kotlin/com/tencent/kuikly/core/annotations/Page.kt
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Page(val name: String = "", val supportInLocal: Boolean = false, val moduleId: String = "")
  • 上下文入口与页面存在性检查:core-render-android/src/.../KuiklyRenderJvmContextHandler.kt
// 文件:core-render-android/src/main/java/com/tencent/kuikly/core/render/android/context/KuiklyRenderJvmContextHandler.kt
private val kuiklyClass = Class.forName("com.tencent.kuikly.core.android.KuiklyCoreEntry")
fun newKuiklyCoreEntryInstance(): IKuiklyCoreEntry = kuiklyClass.newInstance() as IKuiklyCoreEntry
fun isPageExist(pageName: String): Boolean {newKuiklyCoreEntryInstance().triggerRegisterPages()return BridgeManager.isPageExist(pageName)
}
  • 说明:KuiklyCoreEntry 由注解处理器在构建期生成(参见 core-kapt/.../AndroidTargetEntryBuilder.kt),用于把跨平台层定义的 @Page 页面注册到桥接管理器中;Android 容器通过 ContextCodeHandler 初始化委托并打开页面。

两棵树的具体实现(Kotlin 构建与原生承载)

Kotlin 树(声明式节点与布局)

  • 节点模型:每个组件都是 DeclarativeBaseView<Attr, Event>,持有布局节点 flexNode 与属性对象 Attr,并维护子节点 domChildren(Kotlin 侧的“DOM 树”)。
  • 构建关系:在 DSL 渲染阶段按自顶向下插入子节点,既更新 Kotlin 树,也准备对应的原生承载关系。
// compose/src/.../ui/node/KNode.kt:构建 Kuikly 节点树(Kotlin 树)
fun insertTopDown(index: Int, instance: KNode<DeclarativeBaseView<*, *>>) {val childView = instance.viewcurrentView.addChild(childView, instance.init, index)     // 建立 Kotlin 树父子关系currentView.insertDomSubView(childView, index)            // 维护 domChildren(声明式层)
}
  • 属性聚合与派发:AbstractBaseView.didSetProp 在属性变化时把键值派发到承载的 RenderView,再经桥接送达原生控件。
// core/src/.../AbstractBaseView.kt:属性更新从 Kotlin 层派发到 RenderView
open fun didSetProp(propKey: String, propValue: Any) {renderView?.setProp(propKey, propValue)
}// core/src/.../RenderView.kt:属性与事件通过 BridgeManager 发送到原生
fun setProp(key: String, value: Any) {BridgeManager.setViewProp(pagerId, viewRef, key, value, 0)
}
fun setEvent(eventName: String, sync: Int) {BridgeManager.setViewProp(pagerId, viewRef, eventName, 1, 1, sync)
}
  • 布局与位置:布局完成后把框架(x/y/width/height)设置到原生视图,以便平台端完成摆放与绘制。
// core/src/.../RenderView.kt:同步框架到原生
fun setFrame(x: Float, y: Float, width: Float, height: Float) {BridgeManager.setRenderViewFrame(pagerId, viewRef, x, y, width, height)
}

原生树(平台视图的创建与承载)

  • 创建与插入:每个 Kotlin 视图会对应一个原生视图,通过 RenderView 驱动创建与插入到父视图(“原生树”)。
// core/src/.../RenderView.kt:创建与插入原生视图
init { BridgeManager.createRenderView(pagerId, viewRef, viewName) }
fun insertSubRenderView(subViewRef: Int, index: Int) {BridgeManager.insertSubRenderView(pagerId, viewRef, subViewRef, index)
}// core/src/.../ViewContainer.kt:把子视图的 RenderView 插入到父 RenderView(原生树)
open fun insertSubRenderView(subView: DeclarativeBaseView<*, *>) {currentRenderView()?.renderView?.let { renderView ->val sub = renderViews(subView)val children = currentRenderView()?.renderChildren()sub.forEach {val insertIndex = children.indexOf(it)it.createRenderView()renderView.insertSubRenderView(it.nativeRef, insertIndex)it.renderViewDidMoveToParentRenderView()}}
}

更新机制(从 DSL 到原生控件)

  • 属性更新链路:attr { ... } 修改属性 → didSetPropRenderView.setPropBridgeManager.setViewProp(...)NativeBridge.toNative(...) → 平台端执行 setXxx 或绘制逻辑。
  • 布局更新链路:Flex 布局计算出新 FrameRenderView.setFrame(...)BridgeManager.setRenderViewFrame(...) → 平台端重排与重绘。
  • 结构更新链路:新增/移动/删除子节点 → ViewContainer.insertSubRenderView(...)/removeBridgeManager.insertSubRenderView(...)/removeRenderView(...) → 平台端维护原生树结构。
// core/src/.../BridgeManager.kt:统一的原生调用入口(属性/框架/结构)
fun setViewProp(instanceId: String, tag: Int, propKey: String, propValue: Any, isEvent: Int, sync: Int) {callNativeMethod(NativeMethod.SET_VIEW_PROP, instanceId, tag, propKey, propValue, isEvent, sync)
}
fun setRenderViewFrame(instanceId: String, tag: Int, x: Float, y: Float, w: Float, h: Float) {callNativeMethod(NativeMethod.SET_RENDER_VIEW_FRAME, instanceId, tag, x, y, w, h)
}
fun insertSubRenderView(instanceId: String, parentTag: Int, childTag: Int, index: Int) {callNativeMethod(NativeMethod.INSERT_SUB_RENDER_VIEW, instanceId, parentTag, childTag, index)
}
  • 角色分工(两棵树直调):
    • Kotlin 树负责声明式描述、属性聚合、布局计算与增量更新的触发;
    • 原生树负责承载与绘制;属性与框架变化直达原生,无需虚拟 DOM 全量 diff,减少跨语言序列化与调度开销。

提示:AttrFlexNode 在 Kotlin 层分别承担“样式/属性聚合”和“布局测量与定位”;nativeRef 则是跨端唯一 ID,用于把 Kotlin 节点与原生视图一一对应。

桥接调用链(多端统一)

  • Kotlin 通用桥接:core/src/commonMain/.../BridgeManager.kt
// Kotlin 层调用 NativeBridge,将通用方法参数直达平台渲染器
private fun callNativeMethod(methodId: Int, arg0: Any?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?, arg5: Any?): Any? {callObserverMap[currentPageId]?.safeOnCallNative(methodId, arg0, arg1, arg2, arg3, arg4, arg5)return nativeBridgeMap[arg0 as String]?.toNative(methodId, arg0, arg1, arg2, arg3, arg4, arg5)
}
  • iOS 桥接实现:core/src/iosMain/.../NativeBridge.kt
// iOS 侧通过委托把通用方法转到 UIKit 渲染路径
actual open class NativeBridge actual constructor(){var iosNativeBridgeDelegate: IOSNativeBridgeDelegate? = nullactual fun toNative(methodId: Int, arg0: Any?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?, arg5: Any?): Any? {return iosNativeBridgeDelegate?.callNative(methodId, arg0, arg1, arg2, arg3, arg4, arg5)}
}
  • 鸿蒙桥接实现:core/src/ohosArm64Main/.../NativeBridge.ohosArm64.kt
// OHOS 侧通过回调把方法转入 ArkUI C-API 渲染器
actual open class NativeBridge actual constructor(){var callNativeCallback: CallNativeCallback? = nullactual fun toNative(methodId: Int, arg0: Any?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?, arg5: Any?): Any? {return callNativeCallback?.invoke(methodId, arg0, arg1, arg2, arg3, arg4, arg5)}
}

属性映射与适配器(工程要点)

  • Android:docs/QuickStart/android.md 中提供图片、日志、异常、颜色、字体等适配器接口,宿主实现后平台层仅承载“绘制与属性映射”。
// 日志适配器示例(源码节选)
object KRLogAdapter : IKRLogAdapter {override val asyncLogEnable: Boolean get() = trueoverride fun i(tag: String, msg: String) { Log.i(tag, msg) }override fun d(tag: String, msg: String) { Log.d(tag, msg) }override fun e(tag: String, msg: String) { Log.e(tag, msg) }
}

// iOS/Harmony 对应通过 Render 模块暴露的适配器入口初始化(参见 Package.swift 指向 core-render-ios 与 docs/QuickStart/harmony.md)。

为什么又谈跨平台渲染

  • Android 团队在“性能、内存、生态、一致性、混合能力”之间反复权衡。主流方案分两类:
    • 原生渲染(ReactNative/Hippy):沿用平台控件与渲染管线,生态与混合开发友好,但 Native 侧逻辑复杂、跨端 UI 细节容易不一致。
    • 自绘渲染(Flutter/Compose):单画布自绘,UI 一致性强,但冷启与内存成本更高,混合原生控件时存在层级与同步问题。
  • 实践结论:原生渲染除 UI 一致性劣势外,其它方面普遍更优;关键在于“如何提升原生渲染的一致性”而不牺牲生态与性能。

原生渲染为什么会不一致(问题根源)

  • 典型 RN 方案在跨平台层维护虚拟 DOM 树,通过 Diff 驱动 C++ Shadow 树测量布局,再同步到 Native 控件树进行绘制。
  • 由于跨 JS/C++/Java/OC 多层通信与大量 Native 侧 UI 逻辑(尤其列表等高阶组件),不同平台的实现细节与边界条件容易分叉,最终表现出 UI 不一致。
  • 这类“三棵树 + 多语言通信 + Native 逻辑重”的设计,是原生渲染一致性难题的根源。

补充科普:什么是“三棵树”(以 RN 类方案为例)

  • 三棵树指的是跨平台原生渲染方案中常见的三层 UI 结构:
    1. JS 侧的“虚拟 DOM 树”(Virtual DOM)
    2. C++/底层的“Shadow 树”(布局测量树,通常由 Yoga/Flexbox 等实现)
    3. 平台侧的“原生 View 树”(Android 的 View/ViewGroup 层级)

1) 虚拟 DOM 树(JS 侧)

  • 在 React/JSX 中,页面以组件树的形式构建;运行期会维护一棵 Virtual DOM,用于描述 UI 的结构与属性,并通过 Diff 生成更新补丁。
// JS/React 侧:组件与虚拟 DOM 结构
function App() {return (<View style={{ flex: 1, padding: 8 }}><Text style={{ color: '#0a0' }}>Hello RN</Text></View>);
}// 运行期可能对应的虚拟 DOM(示意)
const vdom = {type: 'View',props: { style: { flex: 1, padding: 8 } },children: [{type: 'Text',props: { style: { color: '#0a0' } },children: ['Hello RN'],},],
};

2) Shadow 树(布局测量树)

  • 虚拟 DOM 的变更会通过 Bridge 驱动到 Shadow 树;Shadow 节点保存样式与布局信息,经 Flexbox 算法完成测量与排版,得到每个节点的最终位置与尺寸。
// C++/Yoga 侧:Shadow 节点与布局(示意伪代码)
struct ShadowNode {Style style;            // 包含 flexDirection, justifyContent, padding 等std::vector<ShadowNode*> children;Layout layout;          // 布局计算结果:x, y, width, height
};void calculateLayout(ShadowNode* root) {// 递归执行 Flexbox 布局测量,填充每个节点的 layout// ...
}// 更新样式后重新计算
void updateStyle(ShadowNode* node, const Style& s) {node->style = s;// 标记为脏,后续触发重新计算
}

3) 原生 View 树(平台控件层)

  • Shadow 树计算完成后,会将每个节点映射到平台原生控件(Android 的 View/ViewGroup),最终由原生渲染管线绘制到屏幕。
// Android 侧:根据 Shadow 布局结果创建并设置原生 View(示意)
val root = LinearLayout(context).apply { setPadding(dp(8)) }
val text = TextView(context).apply {setTextColor(Color.parseColor("#0a0"))text = "Hello RN"
}
root.addView(text)// 根据 Shadow 树的 layout 结果,设置位置与尺寸(示意)
fun applyLayout(view: View, layout: Layout) {view.layout(layout.x, layout.y, layout.x + layout.width, layout.y + layout.height)
}

三棵树的协同流程(简化)

  • JS 组件更新 → 生成虚拟 DOM Diff → 通过 Bridge 转为 Shadow 树的样式/结构变更 → Shadow 树执行布局测量 → 将结果映射并同步到原生 View 树 → 原生渲染管线绘制。

为何容易出现多端不一致(关键差异源)

  • 多语言与多实现:JS/C++/Java/OC 横跨多层,边界条件与细节实现容易分叉。
  • Native 侧逻辑重:列表复用、文本测量、动画排程等若在平台层分别实现,差异不可避免。
  • 复杂场景复合:在自绘/原生混合或高阶组件中,层级合成与事件同步更易出现差异。

Kuikly 的应对(两棵树 + 直调)

  • 把“Build/Measure/Layout”等逻辑统一收敛到 Kotlin 跨平台层,只在平台侧保留“绘制与属性映射”。
  • 以通用原子接口直调平台控件,避免跨语言序列化与多层 Diff 带来的开销与不一致。
// Kotlin 跨平台层:构建唯一 UI 原型树并直调平台适配器(示意)
interface NativeOps {fun create(type: String, id: Int)fun setAttr(id: Int, key: String, value: Any)fun addChild(parentId: Int, childId: Int, index: Int)
}// Kuikly DSL 构建页面并通过 NativeOps 映射到 Android 原生控件
Page {attr { width(100.dp); height(50.dp) }Text { attr { text("Kuikly"); color(Color.GREEN) } }
}.render(nativeOps) // 直调平台,O(1) 同步 UI 更新

Android 渲染管线与 Skia(深入科普)

  • Android 的图形渲染自底向上由 Skia 提供 2D 绘制能力,结合硬件加速(GPU/OpenGL/RenderThread),最终呈现到屏幕。
  • 应用层的开发者通常不直接调用 Skia C++ 接口,而是通过 Android 框架的 View/Canvas/Paint 等高级 API 使用其能力;框架在底层把这些调用映射到 Skia。
  • 两条常见路径:
    • 组件渲染路径:使用 TextView/LinearLayout/RecyclerView 等原生组件,框架内部完成绘制,这些组件自身依赖 Canvas/Skia;开发者以“属性 + 组合”方式构建 UI。
    • Canvas 自绘路径:自定义 View 并重写 onDraw(Canvas),直接用 canvas.drawRect/drawText 等进行绘制,仍旧通过框架把调用映射到 Skia。

Kuikly 在 Android 的渲染选择(NA 渲染到底是什么)

  • Kuikly 的 NA(Native)渲染是“以原生组件与原生渲染管线为主”的方案:
    • 不在跨平台层直接调用 Skia 的 C++ 接口;跨平台层(Kotlin)只负责构建、测量、布局并通过通用接口把属性映射到 Android 原生组件。
    • 原生组件负责最终绘制;必要时可通过自定义 ViewonDraw 使用 Canvas 完成图形绘制,这也仍属于原生渲染路径(底层仍是 Skia)。
  • 与自绘引擎(Flutter/Compose)不同:Kuikly 不嵌入独立的 Skia 引擎管线,也不维护一套自有“画布 + 合成树”,而是最大化利用 Android 的原生组件与渲染体系 [0]。

示例:组件渲染 vs Canvas 渲染(Android 侧)

// 组件渲染:使用原生 TextView(框架内部使用 Canvas/Skia 绘制文本)
val title = TextView(context).apply {text = "Kuikly"setTextColor(Color.parseColor("#00AA00"))textSize = 20f
}
container.addView(title)// Canvas 自绘:自定义 View 并重写 onDraw(仍经由框架调用 Skia)
class BadgeView(context: Context): View(context) {private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {color = Color.REDtextSize = 28f}override fun onDraw(canvas: Canvas) {// 绘制圆形徽章与文字canvas.drawCircle(width / 2f, height / 2f, 24f, paint)canvas.drawText("Hot", 16f, 36f, paint)}
}
container.addView(BadgeView(context))

Kuikly 的直调与属性映射如何落在 Android

  • Kuikly 在 Kotlin 层通过 NativeOps 这样的通用接口:
    • 创建控件:create("TextView", id)
    • 设置属性:setAttr(id, "textColor", "#00AA00")
    • 组装层级:addChild(parentId, childId, index)
  • Android 侧把这些“类型 + 属性 + 层级”映射为原生控件与对应的 View API 调用;渲染由 Android 原生管线完成。

为什么不直接在跨平台层调用 Skia

  • 直接驱动 Skia 意味着要维护独立渲染管线、事件分发与合成树,这会提高工程复杂度、内存开销与混合开发门槛。
  • Kuikly 的目标是在不牺牲原生生态与混合能力的前提下统一逻辑层:把“建树/测量/布局”收敛到跨平台层,把“绘制”留给平台原生,从根上减少跨端差异源 [0]。

原生组件的优势与典型场景(Android)

  • 列表与复用:RecyclerView 在性能与生态上成熟,Kuikly 可通过跨平台层统一列表的测量与数据驱动,平台侧承载复用与滚动。
  • 视频与图像:SurfaceView/TextureView/ImageView 等原生控件在兼容与硬件加速上更可靠,便于与系统能力无缝集成。
  • 输入与窗口:与输入法、窗口 Insets、焦点管理保持原生一致,降低跨平台与系统交互的坑位。

与自绘方案对比(工程视角)

  • 自绘(Flutter/Compose):
    • 优点:统一的自绘画布,跨端 UI 一致性强。
    • 成本:冷启与内存较高,PlatformView 混合存在层级合成与同步问题,需维护引擎与合成树。
  • Kuikly NA 渲染:
    • 优点:沿用原生渲染管线,冷启与内存更友好,原生混合能力强;跨平台层统一逻辑减少差异源。
    • 取舍:UI 一致性依赖属性映射的完整性与精确性;需要在 Kotlin 层实现一致的测量/布局/状态管理 [0]。

结论(回答“NA渲染是否直接调用 Skia”)

  • Kuikly 的 NA 渲染不在跨平台层直接调用 Skia C++ 接口;而是以原生组件与 Canvas 路径完成绘制,底层由 Android 框架把调用映射到 Skia。
  • Kuikly 把跨端逻辑统一在 Kotlin 层(两棵树直调),平台层只做绘制与属性映射,从工程层面提升一致性与可控性,同时保留原生生态优势 [0]。

FAQ:NA 渲染是否直接调用 Skia?

  • 结论:不直接在跨平台层调用 Skia;Android 侧通过原生组件与 Canvas 使用框架能力,框架底层再映射到 Skia。
  • 与自绘方案的区别:Flutter/Compose 会维护自己的绘制/合成管线并直接驱动 Skia;Kuikly 没有嵌入独立 Skia 引擎,沿用 Android 原生渲染。
  • 选择何时用组件 vs Canvas:
    • 文本/列表/图片/视频等成熟场景优先原生组件(TextView/RecyclerView/ImageView/SurfaceView)。
    • 简单形状/徽章/装饰等可用自定义 View.onDraw(Canvas)
  • 示例(Canvas 路径):
class TagView(context: Context): View(context){private val p = Paint(Paint.ANTI_ALIAS_FLAG).apply{ color = Color.BLUE; textSize = 24f }override fun onDraw(canvas: Canvas) {canvas.drawRoundRect(0f, 0f, width.toFloat(), height.toFloat(), 8f, 8f, p)canvas.drawText("Kuikly", 16f, height/2f + 8f, p)}
}

Kuikly 的思路:把“逻辑一致性”前置到跨平台层

  • 关键理念:把“建树(Build)/测量(Measure)/布局(Layout)”上收至 Kotlin 跨平台层统一实现,仅把“绘制(Draw)”留给各平台原生控件。
  • 直调架构替代虚拟 DOM:避免跨语言序列化/反序列化与复杂 Diff;Kotlin 侧维护唯一 UI 原型树,O(1) 同步 UI 更新。
  • 降低 Native 逻辑密度:借鉴 KMM + MVVM,把组件的状态与业务逻辑(ViewModel)统一在跨平台层实现,Native 侧只做原子控件属性映射与承载。

两棵树直调架构(心智模型)

  • Kotlin 树(跨平台层):负责 UI 的构建、测量、布局与状态管理;通过通用“增删改”原子接口直调平台侧。
  • Native 树(平台层):由原生控件组成,仅承担绘制与承载;不包含跨端业务逻辑,减少多端分叉来源。
  • 属性映射统一:把颜色、字体、尺寸、边距、布局约束、交互回调等以统一接口映射到原生控件,平台层无需重复实现复杂 UI 逻辑。

为什么这套架构能提升一致性

  • 逻辑统一:复杂测量/布局/状态/Diff 统一在跨平台层实现,平台层只保留绘制,减少差异源。
  • 映射清晰:通用接口保证属性到原生控件的一致映射路径,易于审查与回归。
  • 混合能力稳健:沿用原生渲染管线,避免自绘画布中 PlatformView 的层级与同步问题(列表滚动与嵌入原生播放器的典型痛点)。

Android 侧工程化落地

  • 心智模型:把 Kuikly 视为“跨平台 UI 引擎(Kotlin 层)+ Android 宿主容器与适配器(平台层)”。
  • 接入步骤:
    • 引入 Kuikly 依赖:跨平台产物(core)与 Android 渲染器(core-render-android)。
    • 实现宿主容器:KuiklyRenderActivity 用于承载 Kuikly 页面;准备颜色、字体、图片、日志等适配器。
    • 页面路由与跳转:在 Kotlin 侧以注解或 DSL 定义页面,Android 通过 KuiklyRenderActivity.start(context, pageName, params) 跳转。
  • 适配器机制(按需实现):
    • 颜色解析:自定义字符串到 ARGB 的转换策略,统一品牌色与暗色模式等。
    • 字体与图片:接入团队现有加载与缓存体系,控制主线程与 IO 压力。
    • 日志与网络:打通监控与埋点,便于性能回溯与问题定位。

最小示例(概念演示)

@Page("test")
class TestPage : Pager(){override fun body(): ViewBuilder {return {attr { allCenter(); backgroundColor(Color.WHITE) }Text { attr { fontSize(20f); color(Color.GREEN); text("Hello Kuikly") } }}}
}// Android 侧启动
KuiklyRenderActivity.start(context, "test", JSONObject())

性能、内存与冷启表现(经验法则)

  • 冷启:不引入自绘引擎初始化成本,沿用平台渲染管线;对业务 App 更友好。
  • 内存:不维护自绘合成树,内存占用更接近原生;复杂列表与图片策略由跨平台层统一控制,避免多端各自为战。
  • 监控点位:首帧时间、页面创建耗时、渲染阶段耗时等;建议在容器与适配器中统一打点,形成端到端视图。

与原生的混合与协作

  • Fragment/Activity 共存:Kuikly 页面与原生模块可互跳,生命周期与窗口系统保持一致。
  • 原生能力直通:视频、硬件加速、系统组件(RecyclerView、SurfaceView)可与 Kuikly 页面混合使用。
  • UI 复用:通用属性接口让样式与布局可复用,减少双轨维护成本。

从 RN/Flutter 迁移的策略建议

  • RN → Kuikly:列表、复杂布局与交互路径收益显著;评估自定义组件的属性映射与事件模型迁移成本。
  • Flutter → Kuikly:冷启、内存与原生混合能力更优;UI 一致性以跨平台属性映射“准确/完整”为保障。
  • 迁移节奏:
    • 先迁易后难:选“结构稳定、性能敏感”的页面试点。
    • 监控为先:把首帧、列表、图片等关键点位打全,建立回归基线。
    • 适配器补齐:颜色/字体/图片/日志/网络统一到位,减少隐形差异。

参考链接

  • Kuikly 跨平台 UI 渲染架构详解(原理与对比):https://kuikly.tds.qq.com/Blog/architecture/kuikly-rendering.html
http://www.dtcms.com/a/478865.html

相关文章:

  • 知识就是力量——Docker 快速入门
  • 国际带宽增长与用户体验下降的悖论
  • 怎么让别人做网站看片狂人
  • 粉末涂料做网站有用吗怎么看网站的备案信息
  • 搭建Vue3工程(去除不必要的文件)
  • javaWeb-前端初识-html、css-网页标题制作
  • python的进程间通信
  • 【思考】结构化地让大脑反复“重新理解”知识
  • 百度网盘不限速下载网站(完全免费)
  • 做网站的销售团队东莞市路桥收费所
  • 浅谈 自适应学习
  • 丰都县网站安卓系统开发工具
  • ◆comfyUI教程◆第2章06节 controlnet基础控制类型-线条类
  • DQPSK 调制的基本原理
  • 网络协议分层:解密TCP/IP五层模型
  • 邢台集团网站建设html登录页面代码
  • 【开题答辩全过程】以 办公耗材采购与领用管理系统设计与实现为例,包含答辩的问题和答案
  • 重生之我在大学自学鸿蒙开发第三天-《三层架构》
  • 深入linux的审计服务auditd —— 筑梦之路
  • 杭州拱墅网站建设软文发布平台乐云seo
  • AL2系统下编译安装PSQL16.4版本
  • wrapper+ xml文件进行SQL编写
  • sql题目练习——聚合函数
  • 鄂尔多斯网站开发广东深圳龙岗区天气
  • 运用photoshop设计网站首页北京vi设计公司怎么样
  • Day61 Linux内核编译、裁剪与驱动开发基础
  • 哪个网站可以做ppt模板深圳网站设计公司费用是
  • 中国十大发布信息网站排名2021年9月重大新闻
  • VBA数据结构性能革命:Dictionary与Collection的终极对决
  • 从 0 到 1 理解读者写者问题与读写锁:操作系统并发编程入门