0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙跨端实践-JS虚拟机架构实现

京东云 来源:京东科技 杜强强 作者:京东科技 杜强强 2024-09-30 14:42 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

作者:京东科技 杜强强

前言

Roma跨端方案中,JS虚拟机是框架的核心,负责执行动态化的JS代码。在Android平台采用了基于V8的J2V8,iOS平台则使用了系统自带的JSCore,而在HarmonyOS中,由于业界无类似的框架,我们需要自行实现以确保核心基础能力的完整。 鸿蒙虚拟机的开发经历了从最初 ArkTs2V8JSVM + Roma新架构方案。在此过程中,我们实现了完整的鸿蒙版的“J2V8”和 基于系统JSVM的JS虚拟机框架,解决了JS引擎库移植、多语言通信能力、多类型数据结构转换等众多挑战。本文将从实现的各个阶段过程出发,探讨在实践中遇到的问题及解决方案。

一、鸿蒙版 “J2V8”虚拟机实现 - ArkTs2V8

ArkTs2V8框架依赖V8引擎, 鸿蒙前期交叉编译资料少,V8官方也未有HarmonyOS端编译方式。因此在这过程中, 我们采取初期使用QuickJS引擎(C语言开发,代码少,移植方便), 后期自编译V8完成后替换QuickJS, 保证快速验证跨端前期技术调研方案以及其他依赖项基础能力的开展。 自编译V8 通过学习交叉编译相关技术,摸索式逐步解决编译期间这种报错,完成V8虚拟机移植。

ArkTs2V8 架构借鉴了Android J2V8(动态化-J2V8文章中讲述了具体原理及实践)的实现原理。 J2V8为针对V8的 Java实现,采用最直接的方式在Java中访问V8原始值,因此具备较高的性能。 在HarmonyOS中,采用V8作为JS引擎, JSI作为通信层完成设计。

wKgZomb6SCqAaKbAAACBgMlQU1Y485.png

1、引入JSI

考虑到跨端框架的未来发展,虽然通过C++ 能够直接与V8交互,但这种方式不利于虚拟机代码的共享和扩展。因此Roma框架引入JSI,以增强代码的可扩展性,促进更有效的代码共享,并实现更灵活的虚拟机集成。

JSI(JavaScript Interface),轻量级,通用且同步的JavaScript接口, 通过JSI,JS代码可以直接与C++原生代码通信。

有了JSI层对虚拟机的封装,Roma框架开发者无需在关心虚拟机底层能力, 同时也可以自由切换引擎,比如使用V8,QuickJS、JSVM等, 规范了数据格式,统一为JSIValue。

2、API与框架设计原理

接口设计采用和J2V8 类似的设计,支持多虚拟机实例方式。

wKgaomb6SCuAD_36AAKvXfDR4Es416.png

实现原理:

1、本地接口: 使用 napi 使用创建桥梁, 完成本地代码调用Quick引擎函数。

2、C++数据绑定:在C++层面 ,定义虚拟机交互操作的相关函数,完成V8引擎相关API 来执行JS代码、 处理JS对象和执行虚拟机相关的操作。

3、JSIRuntime: 在C++层面引入JSI概念,通过完成JSIRuntime - QuickJSRuntime & V8Runtime, 完成虚拟机层通信能力。

4、虚拟机对象的定义及封装:根据JS数据类型,定义ArkTS数据结构,包括基本数据类型、JSObject、JSArray、JSFunction。ArkTS侧 类型对象持有C++ JSIValue 对象指针,当执行具体能力时,通过napi 传递指针,完成具体功能的调用。 简单来说,相当于ArkTS JS对象代理C++ 虚拟机数据对象。

5、内存管理: ArkTs2V8负责管理ArkTS与JSValue 之间的内存交互。其中C++侧完成JSValue对象的创建、引用持有与销毁。 ArkTS数据对象中定义对象释放函数, 数据使用完后,由ArkTS调用释放内存。

ArkTs2V8架构设计支持虚拟机多实例, 单个虚拟机的创建过程时由 ArkTs通过JSEngine发起创建JSRuntime虚拟机实例创建,经过napi,在C++环境创建JSRuntime引擎实例及引用, 并完成环境Context及global的初始化, 同时创建ArkTs JSRuntime对象,代理C++虚拟机对象JSRuntime(QuickJSRuntime or V8Runtime) 并绑定指针引用。

初始化过程:

wKgZomb6SC-AFyHvAASL8DdSAm0656.png

V8Runtime实现

wKgaomb6SDCAc3qVAAXsK5AwqNQ365.png

3、JS、JSI、JSRuntime 关系

wKgZomb6SDGAKce9AAAx9EXVuNo989.png

JSRuntime (QuickJSRuntime or V8Runtime) 是 JS运行时环境。一个 JSRuntime 通常包括一个或多个引擎,JSI 可以看作是连接 JS 代码和 JSRuntime 的桥梁。通过 JSI,开发者可以更直接地与 JSRuntime 交互,实现原生功能的调用和管理。

4、部分过程剖析

ArkTs2V8实现的过程中,最基础的两个功能原理:JSObject对象的创建与获取、原生方法的注入, 这两个能力的实现可以扩展到其他大多数API功能实现上。

1、JSObject对象及获取对象数据过程。

通过JSRuntime 发起接口的调用,通过napi,根据对象类型在C++侧创建对象的JSValue对象及象指针引用, 并将引用指针绑定至ArkTS对象,完成对象的创建。

wKgaomb6SDKAAnj7AASzY6VbRIA936.png

2、 JS虚拟机注入原生方法

ArkTS方法到JS虚拟机中,主要实现原理:

将ArkTs的方法 和 目标注册对象指针 生成MethodDescriptor方法描述对象, 通过functionID将对象存储在当前JSContext环境中。 通过napi 发起在C++侧代理函数HostFunction的创建,并绑定ArkTs的方法的引用。 进入到JSI内部,创建方法代理HostFunctionProxy 对象,绑定代理方法HostFunction及守护函数Finalizer, v8::External 将HostFunctionProxy与 JS环境对象(V8对象) 关联起来,生成V8 Function , 此时V8函数会与HostFunctionProxy生命周期绑定。 简单来说相当于ArkTS callback,传递至C++,C++创建JSI Callback并绑定ArkTS callback, JSI Callback 设置到HostFunctionProxy中,HostFunctionProxy 通过 v8::External与 JS环境绑定。

当JS触发该该函数时,通过v8::External绑定HostFunctionProxy这层关系,HostFunctionProxy中JSI Callback会收到JS环境的响应消息,在通过绑定的ArkTs的方法 通过napi接口返回至ArkTS中,最终ArkTS收到方法响应。

这种代理函数的实现, 初次学习可能比较复杂,但整个过程实际是多个对象间引用的持久化和不同数据对象的交换, 大致过程图如下:

wKgZomb6SDKAPD2bAAJcvMkwtzI011.png

4、问题及挑战

1、 数据对象的内存管理

手动内存管理。 ArkTs2V8 负责管理ArkTS与V8之间的内存交互中,ArkTs发起对象的创建和销毁。 整个内存的管理是基于手动管理,需使用方用完后及时关闭,避免内存泄露。 这种设计模式下,使用者操作不当极为容易造成内存泄露,并且使用也较为不便。

针对这问题,在后续的迭代设计中,将内存管理升级为自动内存管理的方式。 JS为单线程执行,单方法片段或一些逻辑中,如果有了调用开始时机结束调用时机, 通过开始时记录当前时刻后开始创建的对象,在调用结束时刻对记录的对象进行统一的内存释放,类似于标记垃圾回收,完成内存的统一管理。借助Roma框架中对虚拟机层的封装,做到了内存自动管理。

2、 跨语言性能问题

基于ArkTs2V8 的API实现,在原生、JS环境中,无法直接使用对方的数据类型,二者之间数据类型需要转换。 JS到原生的过程中,ArkTs2V8中目前提供的API仅可以获取当前层级的JS对象数据,子对象数据需要通过递归遍历从JS环境中一一获取。因此解析的过程中需要频繁的通过C++读取V8,当数据量较大时通常比较耗时。拿常用的网络模块来说,接口下发的业务接口数据至少都在几K甚至几十K,转为JS对象在中端性能手机上 会有几十ms的耗时,这对单线程模式的JS环境来说影响时巨大的。

ArkTS 、C++ 跨语言通信性能我们可以采取类似于Roma Android的通信次数压缩策略,或者使用JSON序列化来减少跨语言交互的性能损耗, 但无论用哪种都仅是从行为上规避跨语言的性能,而无法彻底解决。

3、 线程管理问题

ArkTS基于TS语言,由于语言特性,ArkTS线程隔离,那么对于ArkTs2V8这种接口设计并不友好。 JS线程需要在ArkTS开启独立worker JS线程,收发JS消息,线程间的隔离,涉及再次序列化数据影响性能。

基于问题2、3 以及对框架未来的思考,Roma鸿蒙端决定采用新的方案: 框架C++化,框架逻辑实现全部放在native侧, 虚拟机实现全部切C++,C++侧完成线程管理,ArkTS不在承担线程和逻辑任务。 这种既提升了解决了问题,提升框架性能,也为今后框架移植其他平台打好基础。

二、基于JSVM虚拟机实现 (Roma新架构)

1、鸿蒙JSVM

在V8移植上,从短期看虽然我们初步掌握了V8交叉编译移植技术,但从稳定性、兼容性、维护成本、包大小等维度看, 采用系统内置虚拟机有巨大的长期收益。 年初Roma框架与华为专家多次沟通交流,最终HarmonyOS将V8内置到了操作系统, Q2我们实现了第三个JSRuntime - JSMVRuntime, 至此鸿蒙动态化架构修改趋于稳定。

JSVMRuntime:

wKgaomb6SDSARTk1AAcPVuz-Khg178.png

2、新架构思路 - Roma架构C++化

新架构的设计思路SDK核心逻辑整体C++侧实现, 这样在底层引擎与核心流程之间可以直接c++通信,线程间上与其他端保持相同 - 三线程模型JS线程 +UI线程 + 耗时计算线程。通过C++ PThread完成线程管理, 从而避免跨语言、ArkTS线程隔离带来的多种性能损耗。 在数据结构设计上, JS数据采用JSI::Value, 与其他线程数据相互交互时, 统一使用folly完成。 另外将虚拟机层下沉,对外提供JSExecutor, 功能开发时框架开发者无需关心虚拟机层的实现。

虚拟机方法与对象的注入上, 通过HostObject代理对象能力的双边映射,原生模块直接与JS 同步或异步交互, 从而缩短了流程链路。

wKgZomb6SDSAYktAAAEheBiR-PY635.png

框架大致原理:

wKgaomb6SDWActkGAADcb0PRQk0717.png

3、过程遇到的

1、JSVM字符串引用问题

JSVMRuntime实现期间,字符串无法创建对象引用。 JSI的设计中将字符串作为 pointer 自定义指针类,通过指针地址访问, 与其相同的还有对象,方法。 在许多语言中字符串都作为一种特殊的类型(非基本数据类型), 例如在C++中,字符是一种基本数据类型,但是字符串不是,字符串由字符组成, V8引擎亦如此。 V8中通常使用v8::String来创建JS字符串, 我们可以对齐进行持久化引用。

而JSVM中 OH_JSVM_CreateReference 无法针对字符串类型创建引用, 字符串的持久化需从JSVM_Value从copy出来通过智能指针或者new内存的方式进行存储,这种copy持久化的方式会造成字符串内存两份(JSVM一份,自己存一份), 实际开发中大量的字符串类型转换,这样会造成内存占比过高。

为此, 经过与华为专家多次交流沟通,最终将字符串归为引用类型,可通过OH_JSVM_CreateReference持久化引用,修改后的方式如下:

wKgZomb6SDaANUhGAAQOni5iYaI833.png

2、HostObject代理对象实现

HostObject 是JS对象,提供与原生直接通信的方式。 相当于 native 在 JS的代理对象,双向映射,原生模块直接与JS 同步或异步交互, 在一些功能实现上可以缩短流程链路, 在JS中可直接调用C++的对象。 在动态化中, 模块的实现采用的就是HostObject能力, 框架层实现模块代理对象及桥通信层面的双向通信过程。 比如登录模块,在ArkTS侧封装模块的API,通过C侧的HostObject映射,可以在JS中直接调用登录模块的登录,退出登录等能力。 HostObject的实现,虽然在框架层面相比于乔通道的方式更加复杂,但对于复杂逻辑流程和交互链路, 基础开发可以更注重于功能逻辑。

HostObject 实现过程较为复杂, 但我们可以将过程拆分,通过对象管理 + 代理函数的方式将过程简化。 首先对象的管理直接JSRuntime中持久化即可,Roma中采用智能指针,那么就剩下代理函数,前面我们讲了JS中注入方法里面包括了代理函数的实现原理,采用类似的思路来完成HostObject。

HarmonyOS提供的JSVM API最初仅支持代理函数的创建, 而我们需要是创建代理对象,对象中可以有任意方法,仅通过代理函数方式无法满足任意方法的需求,为此通过在JS中注入代理对象脚本实现,通过Proxy代理的方式,将get、set等代理对象的方法通过代理函数的方式返回,这种情况下,我们的函数数量就被简化成了get、set及一些固定的方法。 通过这些方法做代理转接,调用到C++对象方法,借助JSI::Value的包装,将具体结果返回。

JS代理脚本部分代码:

wKgaomb6SDeASOeeAAJGHbpsgbY878.png

大致实现过程:

wKgZomb6SDqAdtrVAAGuilqmJkE164.png

示例 - 基于HostObject Console能力实现

wKgaomb6SDyAIMXpAAX31oJPubg888.png

三、总结

0到1实现鸿蒙版“j2v8”、“JSRuntime” 让我们更加了解引擎实现中的各种细节和一些难点问题的解决。 一些方案的实现,也可以延展到其他(非虚拟机)场景。 Roma 框架C++, 让Roma框架走向技术深水区, 为今后capi、未来技术做好了基础,旨在带来更优的性能和更好的用户体验。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JS
    JS
    +关注

    关注

    0

    文章

    79

    浏览量

    18809
  • 架构
    +关注

    关注

    1

    文章

    533

    浏览量

    26506
  • 虚拟机
    +关注

    关注

    1

    文章

    968

    浏览量

    30178
  • 鸿蒙
    +关注

    关注

    60

    文章

    2859

    浏览量

    45357
  • HarmonyOS
    +关注

    关注

    80

    文章

    2146

    浏览量

    35580
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    搭建自己的ubuntu系统之创建ubuntu虚拟机

    打开VMware软件,点击创建新的虚拟机。进入以下界面:选择自定义,点击“下一步”。选择对应VMware版本的兼容性,版本可在帮助->关于VMware Workstation中
    发表于 09-25 09:39

    中软国际鸿云虚拟机安装指南

    虚拟化解决方案,鸿云虚拟机不仅解决了兼容性难题,更通过深度优化的技术架构,让Windows专业软件在鸿蒙环境中获得接近原生的性能表现。
    的头像 发表于 06-23 16:31 2830次阅读

    ArkUI-X平台技术落地-华为运动健康(一)

    、心脏健康)与 数据平台的耦合关系,通过接口的形式隔离三(iOS 和 Android和鸿蒙NEXT系统)的数据平台的实现差异,成为一个重要的问题。所以当前架构设计面临如下三个诉求:
    发表于 06-18 22:53

    TECS OpenStack资源池虚拟机网络二层地址无法互通的问题处理

    某运营商TECS OpenStack使用主机overlay SDN方案组网,运维人员在创建虚拟机测试虚拟机网络状态时发现问题:在其中一台主机上创建两台同网段虚拟机虚拟机之间二层地址无
    的头像 发表于 06-12 09:28 681次阅读
    TECS OpenStack资源池<b class='flag-5'>虚拟机</b>网络二层地址无法互通的问题处理

    ArkUI-x平台Bridge最佳实践

    是Bridge最佳实践中需要讨论的一个重点。 接下来我们以调用相机管理的能力(该能力提供的api当前不支持平台),来介绍平台的Bridge实现“一码三平台”的推荐写法 如上图所示,
    发表于 06-10 23:06

    Kuikly鸿蒙版正式开源 —— 揭秘卓越性能适配之旅

    编译为鸿蒙上可高效运行的Native产物,探索Kotlin JS和Kotlin Native在鸿蒙平台上的适配落地及其性能优化;打通层和
    发表于 06-04 16:46

    hyper 备份,Hyper备份:虚拟机备份与恢复

    的解决方案。今天就为大家介绍Hyper备份:虚拟机备份与恢复。    在虚拟化环境中,备份和恢复虚拟机是确保数据安全和业务连续性的关键操作。Hyper-V提供了多种备份和恢复方法,帮助用户灵活地管理
    的头像 发表于 02-08 09:53 1416次阅读
    hyper 备份,Hyper备份:<b class='flag-5'>虚拟机</b>备份与恢复

    hyper v 上网,Hyper-V上网:虚拟机网络配置

    大家介绍Hyper-V上网:虚拟机网络配置。    在Hyper-V环境中,正确配置虚拟机的网络设置是确保虚拟机能够正常访问外部网络的关键。以下是详细的Hyper-V虚拟机网络配置指南
    的头像 发表于 02-06 10:29 4133次阅读
    hyper v 上网,Hyper-V上网:<b class='flag-5'>虚拟机</b>网络配置

    hyper-v共享,hyper-v共享:实现主机与虚拟机之间高效文件共享的最佳实践

    实现主机与虚拟机之间高效文件共享的最佳实践。    在企业级虚拟化环境中,实现主机与虚拟机之间
    的头像 发表于 01-24 14:23 2374次阅读
    hyper-v共享,hyper-v共享:<b class='flag-5'>实现</b>主机与<b class='flag-5'>虚拟机</b>之间高效文件共享的最佳<b class='flag-5'>实践</b>

    hyper 内存,Hyper内存:如何监控与优化hyper-v虚拟机的内存使用

    :如何监控与优化hyper-v虚拟机的内存使用。    在虚拟化环境中,合理监控和优化Hyper-V虚拟机的内存使用对于提升性能和资源利用率至关重要。本文将详细介绍如何监控Hyper-V虚拟机
    的头像 发表于 01-24 14:15 1677次阅读
    hyper 内存,Hyper内存:如何监控与优化hyper-v<b class='flag-5'>虚拟机</b>的内存使用

    hyper内存条,hyper-v 添加虚拟机还需要硬盘吗

    在计算机技术的浩瀚星空中,虚拟机犹如一颗璀璨的明星,散发着独特的光芒。今天给大家介绍hyper-v添加虚拟机还需要硬盘吗?    hyper-v添加虚拟机还需要硬盘吗?    Hyper-V是
    的头像 发表于 01-24 14:01 697次阅读
    hyper内存条,hyper-v 添加<b class='flag-5'>虚拟机</b>还需要硬盘吗

    #新年新气象,大家新年快乐!#AIGC入门及鸿蒙入门

    ,基于扩散模型,能够生成与给定文本描述相符的图像。 鸿蒙系统入门 1.基础知识:鸿蒙系统(HarmonyOS)是华为推出的一款分布式操作系统,旨在实现设备、
    发表于 01-13 10:46

    AIGC入门及鸿蒙入门

    模型,能够生成与给定文本描述相符的图像。 鸿蒙系统入门 1. 基础知识: 鸿蒙系统(HarmonyOS)是华为推出的一款分布式操作系统,旨在实现设备、
    发表于 01-13 10:32

    AKI语言调用库神助攻C/C++代码迁移至HarmonyOS NEXT

    )开发框架。它极大地简化了JS与C/C++之间的语言访问,为开发者提供了一种边界性编程体验友好的解决方案。通过AKI,开发者可以使用让代码更易读的语法糖,实现JS与C/C++之间的无
    发表于 01-02 17:08

    Ubuntu系统的虚拟机安装步骤 如何在Ubuntu系统上编译软件

    Ubuntu系统上安装虚拟机的步骤 在Ubuntu系统上安装虚拟机是一个相对简单的过程,通常可以通过安装VirtualBox或KVM等虚拟化软件来实现。以下是使用VirtualBox在
    的头像 发表于 12-12 14:36 1865次阅读