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

news/2025/11/11 21:50:53/文章来源:https://www.cnblogs.com/gccbuaa/p/19211690

Kuikly 小白拆解系列 · 第1篇|两棵树直调(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 自绘渲染(定义与取舍)

// 原生组件渲染(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;最大化复用平台渲染管线与生态。

源码速证(三端)

// 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 树(声明式节点与布局)

// 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? = null
actual 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? = null
actual 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() = true
override 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 类方案为例)

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 树 → 原生渲染管线绘制。

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

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.RED
textSize = 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)

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

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

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

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

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

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 侧只做原子控件属性映射与承载。

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

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

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

Android 侧工程化落地

最小示例(概念演示)

@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 更友好。
  • 内存:不维护自绘合成树,内存占用更接近原生;复杂列表与图片策略由跨平台层统一控制,避免多端各自为战。
  • 监控点位:首帧时间、页面创建耗时、渲染阶段耗时等;建议在容器与适配器中统一打点,形成端到端视图。

与原生的混合与协作

从 RN/Flutter 迁移的策略建议

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

参考链接

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/962761.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编程思维与 AI-coding 结合

前言(背景与目标) 最近大量使用 AI 协助完成项目,直观感受是:个人“编码时间”减少,但“完成效率与质量”显著提升。与其担心编程能力下降,不如承认一个事实——角色在升级:从“只写代码的工程师”转向“以目标…

重大收获的一天

重大收获的一天,今天我知道了,JAVA-WEB也是要分文件处理的.可以分为JSP(展示页面),servlet(接受请求,调用业务方法,跳转页面),services(业务逻辑),DAO(与数据库有关的操作), :定义段落,前后会有空白. 仅换行,不添加空白…

如何制作一个随身服务器?

需要在两处办公地点不定时、不定点办公,两边都需要处理文档和查找历史文档,从文档同步角度来说,需要的是:一旦插入U盘,自动更新电脑文件与U盘一致。 要实现这个功能,需要使用FreeFileSync和自带的RealTimeSync工…

业务用例模板(用户线上充值) - f

业务用例模板 一、用例基本信息字段 内容(示例) 填写说明用例名称 用户线上充值 动宾结构,简洁明确用例编号 UC-用户模块-003 模块+序号,便于归类管理参与者 普通用户、支付系统、账户系统 所有与用例交互的角色/系…

丝路杯

这是一个对丝路杯的总结,主要是awd攻防。从这次比赛中找到不足。(之后有补充的话会继续补充) 这次的check很抽象,检测你有没有修改后门文件,修改了,直接异常,只能删除后门文件(无异常)。而且对通防没有限制,…

CTF 流量分析- Wireshark 核心教程:从网卡抓包到 2025 - CTF 流量分析题目技巧

CTF 流量分析- Wireshark 核心教程:从网卡抓包到 2025 - CTF 流量分析题目技巧本文聚焦 Wireshark 的 “操作流程 + 流量筛选”,为网络分析提供实用参考。基础操作环节明确步骤:从网卡列表中选择需监控的网卡启动抓…

关于做过的第一道实验题的思考

> 回忆那是我大一做的第一道实验题 题目内容如下Input:三个int范围内的数字要求:将三个数字从小到大排序后输出...... 或许谈回忆有些太早,毕竟从做这题到现在也就过了短短两个月,但在我逐步学习编程知识的过程…

#20232329 2025-2026-1 《网络与系统攻防技术》 实验五实验报告

1.实验内容 (1)从www.besti.edu.cn、baidu.com、sina.com.cn中选择一个DNS域名进行查询,获取信息。 (2)尝试获取BBS、论坛、QQ、MSN中某一好友的IP地址,并查询获取该好友所在的具体地理位置。 (3)使用nmap开源…

CF round vp 选记

CF2163 D2. Diadrash (Hard Version)有一个隐藏的 \(0\) 到 \(n-1\) 的排列,初始给定 \(n\) 和 \(q\) 个区间 \([l_i,r_i]\),你可以至多询问 \(30\) 次每次查询排列的一个区间的 \(\mathrm{mex}\),用来找到这 \(q\)…

lincon_transformer阅读介绍

核心结论 本文针对消费级设备无法实时运行50∼100B大语言模型(LLM)的问题,提出设备-架构协同设计方案Lincoln,通过优化Flash存储性能和数据传输机制,在不损失模型精度的前提下实现该目标。 背景与痛点现有LLM服务…

完整教程:页表 vs. 组相联缓存:内存管理与性能优化的殊途同归

完整教程:页表 vs. 组相联缓存:内存管理与性能优化的殊途同归pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Co…

2025 年 11 月深圳龙岗网站建设厂家推荐排行榜,外贸独立站推广,阿里巴巴/1688店铺代运营,短视频拍摄运营,商标注册,小程序开发公司精选

2025 年 11 月深圳龙岗网站建设厂家推荐排行榜,外贸独立站推广,阿里巴巴/1688店铺代运营,短视频拍摄运营,商标注册,小程序开发公司精选 行业背景与发展趋势 随着数字化转型浪潮的深入推进,深圳龙岗区作为粤港澳大…

RAG编程实践(DashScope+Milvus)

RAG的实践笔记,在电商客服系统创建一个React模式的智能体,将RAG检索能力以工具的形式提供给模型,使模型能够在需要时主动调用知识库检索功能。目录RAG编程实践一、RAG概述RAG的价值学习资料二、系统RAG架构设计1. 整…

使用 Docker 快速部署 MinIO 文件存储服务

使用 Docker 快速部署 MinIO 文件存储服务本文提供了 Docker 快速部署 MinIO 对象存储服务的完整方案,旨在解决传统文件存储分散、安全性低、运维复杂等痛点。核心步骤包括拉取指定稳定版 MinIO 镜像,通过 Docker 命…

2025 年 11 月财税合规服务厂家推荐排行榜,电商/跨境电商/出口退税/公司注销/股权设计/平台报送/亚马逊/Temu/速卖通/高新企业/审计报告全案解决方案

2025年11月财税合规服务厂家推荐排行榜:电商与跨境电商企业全周期财税解决方案深度解析 行业背景与发展趋势 随着数字经济时代的深入发展,财税合规服务行业正经历着前所未有的变革。特别是在电商与跨境电商领域,企业…

AI智能体落地:Agent-Assist vs 全自动化完整决策指南

今年开始LLM驱动的Agentic AI发展速度非常惊人。而我们现在面临一个实际问题:到底是上全自主的AI智能体,还是让人类继续参与决策?从大量实际案例来看Agent-Assist(也就是Human-in-the-Loop系统)既能带来自动化的效…

详细介绍:微服务时代的前后端协作:API契约驱动开发实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

ZROI-NOIP2025做题记录

一些想起来的要做的东西:切距/曼距转化,以及一些常用旋转角度式子背一背,不要在考场上现推了 楼房重建 checkerDP 观察dp状态和值的关系 7连d1t4 纯性质/打表题 7连d1t2 7连d2t1 7连d2t3 启发式 7连d2t4 鸽巢/糖水 …

week1--RE--刷题记录

week1 刷题 目录week1 刷题1. [GXYCTF2019]simple CPP2. [网鼎杯 2020 青龙组]singal3. [buuctf]firmware4. [2019红帽杯]xx 1. [GXYCTF2019]simple CPP ​ IDA载入直接分析,这个程序的主要加密逻辑有几块:第一块是一…

Appium Inspector教程

{ "platformName": "Android", "appium:deviceName": "Android Device", "appium:noReset": true, "appium:automationName": "UiAutomator2"…