揭秘动态化跨端框架在鸿蒙系统下的高性能解决方案
作者:京东科技 胡大海
前言
动态化跨端框架(后文统称“动态化” )是一个由京东金融大前端团队全自主研发的,一份代码,可以在 HarmonyOS、iOS、Android、Web 四端运行的跨平台解决方案。在研发团队使用后可大幅降低研发人力成本;为业务提供实时触达、A/B 触达等能力以提升业务投放效率;同时保障了 C 端用户优秀的用户体验。
一、动态化跨端框架原理介绍
通过上图,我们先了解一下动态化跨端框架在 iOS、Android 等多个平台实现跨端的原理:
① 业务层: 业务代码经过打包后形成 business.js 发布到云端,被 Harmony、iOS、Android、H5 四端共用。
② 虚拟机层: 虚拟机的核心职责是运行 js 代码,这也是跨平台框架的基础,在 iOS 使用系统内置的 JSCore,在 Android 使用 V8,在浏览器使用 Webkit,在鸿蒙系统我们依然需要一个能够运行 js 代码的虚拟机。
③ 通讯层: 在 iOS 和 Android 端使用了 json 数据格式进行通讯数据的传输,在鸿蒙系统也可以使用该方案。
④ 渲染层: 使用各个系统的系统组件进行 UI 元素的渲染。
二、基于方舟虚拟机的方案探索
1、方舟字节码概念
方舟字节码(Ark Bytecode),是由方舟编译器编译 ArkTS/TS/JS 生成的,提供给方舟运行时解释执行的二进制文件,字节码中的主要内容是方舟字节码指令。
2、在方舟虚拟机中运行 JS
方舟虚拟机不能直接运行当前在 V8 中运行的 js 代码,但是能够执行方舟字节码,所以我们可以借助鸿蒙提供的工具将 js 代码转化为方舟字节码,这样就能利用鸿蒙系统的方舟虚拟机执行我们的 js 代码了。
3、存在的问题
3.1、业务无法热更新
在 iOS 和 Android 端,业务可以随时打包后在云端发布新的版本,借助于 JSCore 或者 V8 就可以直接运行新的版本的 js,这样就支持了业务的热更新发布。但在鸿蒙系统上,华为基于安全考虑,business.abc 这样的字节码文件不支持动态下发,需要内置到 APP 中,这样就失去了业务热更新的能力。
3.2、单线程性能问题
在其他两端我们是开启了一个单独的 JS 线程,进行 business.js 文件的执行,但是如果我们使用方舟虚拟机执行 business.js 转换来的 business.abc 的时候,其实是在方舟虚拟机的 UI 主线程运行了这个文件。在其他两端 js 文件在 JS 线程执行的时候,UI 渲染和交互是并行不受影响的,但是在方舟虚拟机单线程下 abc 文件的执行和 UI 渲染 &交互变成了串行,这样必然会严重影响页面渲染速度和交互的流畅度。
业务不能热更新以及单线程性能不佳等问题的存在,我们决定使用另一种方案-V8 虚拟机。
三、基于 V8 虚拟机的方案落地
1、在 V8 虚拟机中运行 JS
如果能把 V8 移植到鸿蒙系统中,我们就可以像其他两端一样使用多线程并且能实现业务热更新等特性,但是 V8 是一个近千万级代码的庞大仓库,需要掌握 CMake、Clang、LLVM、Ninja 等一系列交叉编译知识(嵌入式范畴),对于应用开发者是一个巨大的挑战,虽然我们已经掌握了 V8 移植到鸿蒙的技术,但从包大小、稳定性、兼容性、维护成本等维度看,华为厂商内置 V8 是一个具有长期收益的重大事项,通过和华为持续沟通,最终华为将 V8 内置到了操作系统,业界所有类动态化框均可直接使用内置 V8 虚拟机进行跨端框架的适配。
2、高性能核心方案
2.1、多线程架构
多线程是提高程序性能最直接、最有效的手段之一,借助于鸿蒙系统内置的 V8 虚拟机,我们就能像 iOS、Android 两端一样使用三线程模型完成动态化跨端框架在鸿蒙系统的渲染过程。
JS 线程负责将业务代码解析为一颗虚拟 Dom 树、发出渲染命令、处理业务逻辑等,通过接口定义的桥方法发送给组件线程进行处理。我们以添加一个点击按钮节点为例,JS 线程会通过“添加节点”这个接口以 JSON 描述的方式,将信息传递给组件线程,组件线程根据 JSON 描述将这个点击按钮节点添加到组件树中,然后触发 UI 线程创建系统组件,比如在鸿蒙系统会创建一个 ArkTS 的按钮组件,在 iOS 系统会创建一个 UIButton 组件。
UI 线程负责用户页面滑动、点击事件等交互行为,当发生比如用户点击事件后,同样通过接口定义的桥方法“调用 JS”,将点击事件传递给 JS 线程进行处理,紧接着继续处理 UI 线程的任务,这样 UI 线程的交互效率就高了,充分保障了用户良好的操作体验。
2.2、JSI 技术引入
通讯桥存在的问题
动态化基于三个线程并行运行的方式,使其渲染性能已经接近于原生的渲染性能,但是在一些频繁通讯场景,通讯桥会“堵塞”,比如当业务需要监听一个页面的滑动而改变另外一个元素背景色的透明度,那么 JS 线程大部分时间在处理接收列表滑动距离,改变元素背景色透明度这个任务中,其他任务的执行会被严重影响。另外 JSON 数据传输的序列化和反序列化过程也会带来很大的线程性能损耗。
解决方案-JSI
之前使用通讯桥的一个主要原因就是 C++ 中的函数没办法完整映射到 JavaScript 中,让 JavaScript 直接调用,所以只能选择以序列化字符串的形式通过通讯桥传输。而 JSI 做的事情就是将 C++ 中的常用类型(函数、对象等)一一映射到 JavaScript 中,我们就能在 JS 中直接调用 C++的函数和对象了。因为消除了桥通讯带来的序列化和异步调用的开销,大大提升了线程通讯性能。
四、进一步优化的方向
1、减少 UI 层级
当前基于多线程和 JSI 的架构模式在鸿蒙系统的性能还算不差,但是在鸿蒙系统上同样一个业务的 UI 层级是其他两端层级的约 2 倍。原因在于在鸿蒙系统使用系统组件进行递归渲染的时候,需要借助自定义组件进行实现,然而和 iOS 和 Android 端的命令式组件渲染不同,比如 RomaDiv 对应 iOS 就是直接翻译为 UIView 即可,在鸿蒙必须增加一个包裹的容器才是一个合法的自定义组件,比如 Stack 容器,这样每个组件的层级就多了一层,层级过多会直接影响渲染性能,在一些复杂业务场景到达一定层级后会造成页面掉帧和卡顿。
面对业界跨端框架面临的这个共性问题,鸿蒙系统提供了 C 语言的命令式接口进行组件创建,C 组件接口是介于 UI 组件的 Native 实现和 ArkTS 对接层之间的一层 C 接口封装,它绕过了状态管理对组件变化、刷新的自动化管理,因此具有较好的性能。同时经过初步验证,接入 C-API 后,UI 层级能直接和另外两端保持一致,同时渲染性能也会得到大幅提升。
2、降低通讯成本
当前 JSI 在鸿蒙系统的应用中通过 JSI 打通 C++,再通过 NAPI 从 C++打通 ArkTS,跨语言通讯成本高。如果接入了 C-API,就避免了 C++和 ArkTS 之间类型互相转换和跨语言调用的开销,也能带来不少的性能提升。
3、JS 逻辑下沉到 C++
在当前架构中,JS 线程运行着 V-Dom 树创建、对比,样式 &属性解析等一系列繁重的框架逻辑,如果我们能将这些 JS 代码逻辑下沉到 C++,框架逻辑运行效率会进一步提升。
总结
本文讲述了如何在鸿蒙系统中实现“动态化”跨端框架的高性能运行。包含探索方舟虚拟机运行方案时遇到的问题,以及基于 V8 虚拟机方案的具体提升手段和后续进一步提升的方案。通过阅读,你将能够更好地理解和应用这些技术,提高跨端框架的性能,提升 C 端用户体验。🚀
PS: 如果你在实践中遇到任何问题或有新的见解,欢迎在评论区中与我分享。期待与你交流!🤝
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/f3024cd58650242f406dde1f3】。文章转载请联系作者。
评论