Kuikly 开发框架笔记

Kuikly 开发框架笔记

Kuikly(Kotlin UI Kit,发音同quickly),是使用Kotlin开发了声明式UI框架,映射到系统原生控件做渲染,最终用KMM(Kotlin Multiplatform Mobile)实现跨端。
Kuikly是一个开发语言高度同源的跨端框架,从业务代码、UI框架、布局层以及渲染层全部使用Kotlin语言(iOS渲染层是OC),这样不仅减少跨语言通信的性能成本,而且开发体验上更纯粹和高效。编译产物上,Android端采用原生的AAR方式,而iOS端通过KMM编译生成.framework,这样就不仅保证了原生开发体验,也保证了原生性能。如果希望实现动态化,Android端可以通过KMM编译成SO,iOS端可以编译成JS(KMM已经可以编译成Wasm,未来有稳定版本后就可以正式使用)。Kuikly具有优异的原生开发体验,相比于Hippy,更符合终端开发习惯。

跨端框架对比

对比维度 H5 Hippy Hippy + 预渲染/预加载 Hippy-SSR + 强缓存 Kuikly
性能表现 首屏 >1300ms 首屏在 800ms~1000ms 首屏 <300ms 非首次 ~350ms
首次 ~800ms
安卓原生 iOS接近原生
方案说明 传统的基于 WebView 的前端开发方案,拥有最广的通用性 Hippy 相对于 WebView 是一个更轻量的 UI 引擎,内存占用只有 20MB,能实现 Hippy 的主进程运行 在 Hippy 的基础上,针对核心页面加入预渲染/预加载能力,进一步提高启动性能 在 Hippy 的基础上引入服务端渲染 + 强缓存能力,能针对所有页面进一步解决非预渲染场景下的启动问题和版本覆盖问题 Hippy 固有的终端+JS 的跨端方案,对于 iOS 端能力受限,需要新的能力来突破前端的 JS 边界,而基于 KMM 的 Kuikly 则是直接建立在纯终端之上,能做到更好的能力扩展
存在问题 问题1:消耗资源多,启动慢(>500ms)
• WebView 内存占用超过 200MB
• 安卓 X5 需要 tool 进程启动,动态预加载 5 分钟内会自动释放,命中率低

问题2:缓存策略不可控
• 只能基于 HTTP 的缓存策略,无法通过编程的方式控制
问题1:版本无法实时更新
• Hippy 通过异步拉取模式进行更新,需要用户二次访问才能生效

问题2:JS 包大小影响启动性能
• Hippy 引擎启动快,但是需要动态载入业务 JS 包,JS 包越大加载启动越慢
问题1:预渲染命中率低
• 动态预渲染的整体命中率不到 10%
• 后端请求放大

问题2:终端资源占用
• 在预渲染模式下,除了加载 Hippy 引擎外还需要运行业务代码,整体内存占用超过 40MB
问题1:首次访问的加载问题
• 首次载入 JS 包时需要请求网络,同时由于没有本地缓存,白屏时间较长

问题2:可交互耗时仍有优化空间
• 服务端渲染能解决首屏问题,但可交互仍需要加载完整的 JS(>1s)

进一步思考:
• 版本覆盖问题
• 动态模式下性能问题
• 能力与接口丰富度
-
优化措施 WebView 启动慢:
• 预加载 tool 进程
• 点击/网络请求并行
• 预截图

缓存策略不可控:
• 升级 HTTP2(server push)
• 离线包提高静态资源缓存命中率
• 基于 PWA 通过编程的方式控制缓存策略
版本覆盖问题:
• 支持预下载能力
• 支持同步更新策略

JS 包大小问题:
• JS 分包策略
• 支持离线包能力
预渲染命中率低:
• 只针对特定入口启动
• 优化预渲染策略:红点+活跃用户

资源占用问题:
• 低端机器降级为预加载
• 长时间不启动自动释放
首次访问无缓存白屏:
• 内置骨架屏+动态数据
• 缓存数据预下发
• 终端强缓存能力

提升可交互耗时:
• 点击/网络请求并行
• JS 分包策略
• JS 内嵌直出能力
• JS 提前载入内存
-
安装包大小 RN7.5MB, Hippy 3.8MB 0.3MB

Kuikly 和 ComposeDSL 的对比

无标题思维导图
最终选择方向 2

Kuikly Compose最终架构方案

对比官方Compose 区别

特性 Kuikly 官方
平台支持 iOS, Android, 鸿蒙、H5、小程序 iOS, Android, PC, H5
动态更新 支持 不支持
渲染层 纯原生 Skia渲染
包体积 较小 较大

Kuikly 架构图

Kuikly 跨端渲染原理


  1. 将 Kotlin 代码编译成各个平台可执行产物
  2. 运行时调用各平台 Native 层渲染接口进行渲染
    1. RN 框架的流程 (三个虚拟树)
      1. 创建JS DOM 树 (平台无关)
      2. C++ 影子树 (平台无关)
      3. 原生渲染树
    2. 问题 - 跨语言序列化反序列化开销
    3. Kotlin 只维护一个树, 直接映射到原生渲染
      1. 在 Kotlin 层构建原型树
      2. 在 Kotlin完成测量和布局(影子树)
      3. 各平台支持统一的渲染接口, 如创建/删除/插入/设置属性/设置节点位置
      4. 转到平台各自原生渲染层,
  3. 原生渲染层, 渲染分为三种类型承接:
    1. View 通用属性
      1. Modifier.border 映射到 View.border
      2. .background 映射到 View.background
      3. .scale 映射到 View.transform
    2. 原子组件
      1. Text () 创建组件 TextView
      2. Image() 创建组件 ImageView
      3. LazyXXX() 创建组件 ScrollView
    3. Canvas 渲染
      1. Canvan { drawRect, drawCircle} 转发原生 CanvasView -> drawRect/ drawCircle

Kuikly DSL语法

  1. 声明式 api: 在原类拓展一个 init 的语法糖, 比如 TextView, 对应语法糖是 Text,
  2. 使用@DslMarker解决不能 Text 不应该嵌套的问题

Diff 性能

对比维度 类RN Flutter Compose SwiftUI
框架类型 跨平台框架 跨平台UI框架 Android声明式UI iOS声明式UI
Diff方案 运行时虚拟Dom Tree Diff 运行时Element Tree Diff 编译时+运行时Diff 编译时+运行时Diff
Diff性能 O(n) O(n) O(1-n) O(1-n)
优化策略 虚拟DOM树对比 Element树对比 编译时优化+运行时增量更新 编译时优化+运行时增量更新

调研结果:现有框架没有完全O(1)的解决方案

Kuikly 解决方案:


if -> vif
else -> velse
elseif -> velseif
when -> vbind
for -> vfor
开发的时候需要额外学习成本, 渲染时候能精确更新, 实现 O(1)的性能

怎么基于 Kotlin实现响应式?

  1. 基于 Kotlin 的属性委托能力 by observable() 将属性变成响应式属性
  2. 属性 getter/setter 触发时候, 触发依赖收集/订阅分发
  3. 只收集单向依赖, 破解死循环

比鸿蒙原生还快


鸿蒙性能优化关键点

  1. llvm 的 CPU Feature参数错误导致内联(inline)生效, 修正后性能提升 30%
  2. 鸿蒙软件模拟了线程私有参数, 导致频繁 throw 的时候性能低下, 提升 30%
  3. GC 优化
-------------本文结束感谢您的阅读-------------

欢迎关注我的其它发布渠道