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

    文章

    78

    浏览量

    18015
  • 架构
    +关注

    关注

    1

    文章

    504

    浏览量

    25410
  • 虚拟机
    +关注

    关注

    1

    文章

    894

    浏览量

    27909
  • 鸿蒙
    +关注

    关注

    57

    文章

    2278

    浏览量

    42601
  • HarmonyOS
    +关注

    关注

    79

    文章

    1951

    浏览量

    29854
收藏 人收藏

    评论

    相关推荐

    鸿蒙实践-长列表解决方案和性能优化

    平台都非常重要。HarmonyOS和iOS类似也提供了自己的解决方案。Roma(罗码)作为端平台,在此基础上进行了具体的实践。在实践过程中,遇到了各种问题和挑战,经历了ArkTS+C++架构
    的头像 发表于 09-23 15:26 185次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>跨</b><b class='flag-5'>端</b><b class='flag-5'>实践</b>-长列表解决方案和性能优化

    史无前例,移植V8虚拟机到纯血鸿蒙系统

    作者:京东科技 于飞跃 一、背景 如图所示,Roma框架是我们自主研发的动态化平台解决方案,已支持iOS,android,web三。目前在京东金融APP已经有200+页面,200+乐高楼
    的头像 发表于 09-18 10:28 785次阅读
    史无前例,移植V8<b class='flag-5'>虚拟机</b>到纯血<b class='flag-5'>鸿蒙</b>系统

    鸿蒙实践-布局方案介绍

    封装到标签中实现,业务只需要针对标签简单地设置相关属性,即可实现列表类布局,大幅提升研发效率。同时动态化也支持绝对布局以及控制视图的显示和隐藏等功能,使之能胜任绝大多数业务布局场景。 在京东金融App使用动态化方案适配鸿蒙系统的
    的头像 发表于 09-18 10:26 726次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>跨</b><b class='flag-5'>端</b><b class='flag-5'>实践</b>-布局方案介绍

    什么是虚拟机?什么是虚拟化?

    在日新月异的科技世界中,虚拟化技术如同一座桥梁,连接着现实与数字的鸿沟,为我们打开了全新的计算维度。虚拟机,这一概念,自其诞生以来,就以其独特的魅力和强大的功能,深深地影响了软件开发、系统测试和云计算等多个领域。即使目前你还不了解它的应用,你应该也听过
    的头像 发表于 09-04 14:55 430次阅读

    虚拟机数据恢复—KVM虚拟机被误删除的数据恢复案例

    虚拟机数据恢复环境: Linux操作系统服务器,EXT4文件系统。服务器中有数台KVM虚拟机虚拟机1:主数据库服务器 虚拟磁盘:系统盘(qcow2)+数据盘(raw)
    的头像 发表于 08-07 13:33 266次阅读
    <b class='flag-5'>虚拟机</b>数据恢复—KVM<b class='flag-5'>虚拟机</b>被误删除的数据恢复案例

    什么是虚拟机虚拟机真的那么好用吗?

    在日新月异的科技世界中,虚拟化技术如同一座桥梁,连接着现实与数字的鸿沟,为我们打开了全新的计算维度。虚拟机,这一概念,自其诞生以来,就以其独特的魅力和强大的功能,深深地影响了软件开发、系统测试和云
    的头像 发表于 07-06 08:05 463次阅读
    什么是<b class='flag-5'>虚拟机</b>?<b class='flag-5'>虚拟机</b>真的那么好用吗?

    鸿蒙】(一)Vmware虚拟机和Ubuntu安装

    备注 虚拟机命名、用户名称、路径不能有汉字 名称或者路径有汉字,导致输入失败或者安装失败 2.虚拟机处理器内核总数(处理器数量 X 每个处理器的内核数量)不得超过电脑逻辑处理器总个数 太少时,下载
    的头像 发表于 02-26 21:27 2800次阅读
    【<b class='flag-5'>鸿蒙</b>】(一)Vmware<b class='flag-5'>虚拟机</b>和Ubuntu安装

    虚拟机ubuntu怎么联网

    虚拟机ubuntu怎么联网  虚拟机(Virtual Machine)是运行在物理机(Host Machine)上的虚拟操作系统环境。在虚拟机中安装Ubuntu操作系统后,需要进行网络
    的头像 发表于 12-27 16:51 845次阅读

    虚拟机怎么进入root用户

    进入虚拟机的root用户,需要通过以下步骤完成。首先,我们需要明确一点,root用户是Linux系统下的超级管理员,具有最高权限。 第一步:打开虚拟机 首先,要确保已经安装了虚拟机软件,并启动
    的头像 发表于 11-23 10:51 1.5w次阅读

    怎么安装linux虚拟机

    在计算机领域,虚拟机是一种软件程序,它允许在主操作系统上运行多个虚拟操作系统。Linux虚拟机在开发、测试和学习等环境中得到广泛应用。本文将详细介绍如何安装Linux虚拟机,并提供一个
    的头像 发表于 11-23 10:50 978次阅读

    Docker与虚拟机的区别

    Docker和虚拟机是两种不同的虚拟化技术,它们在实现方式、资源消耗、运行性能等方面存在许多差异。本文将会详细介绍它们的区别。 一、实现方式 1.1
    的头像 发表于 11-23 09:37 7801次阅读

    虚拟机如何运行c程序

    虚拟机(Virtual Machine,VM)是一种模拟了物理计算机的软件,可以在计算机上创建虚拟的硬件平台,使得用户可以在其中运行操作系统和应用程序。在虚拟机中运行C程序需要进行多个步骤,下面将给
    的头像 发表于 11-17 10:14 4271次阅读

    虚拟机linux怎么编写程序

    虚拟机Linux上编写程序是一项非常常见且有用的任务。无论是开发一个简单的脚本、一个Web应用程序还是一个复杂的软件项目,Linux提供了强大的工具和环境来帮助你进行编程。本文将详细介绍如何在
    的头像 发表于 11-17 10:08 1380次阅读

    linux虚拟机使用教程

    引言:Linux虚拟机是一种方便且常用的技术,它允许用户在现有操作系统的基础上创建和运行另一个独立的操作系统。在本篇文章中,我们将提供一份详尽的教程,帮助你了解如何安装、配置和使用Linux虚拟机
    的头像 发表于 11-17 10:06 1152次阅读

    什么是虚拟机?使用C代码实现一个虚拟机

    本文将教你编写一个自己的虚拟机(VM),这个虚拟机能够运行汇编语言编写的程序, 例如我朋友编写的 2048 或者我自己的 Roguelike。如果你会编程,但希望 更深入地了解计算机的内部原理以及
    的头像 发表于 11-01 10:48 1388次阅读
    什么是<b class='flag-5'>虚拟机</b>?使用C代码<b class='flag-5'>实现</b>一个<b class='flag-5'>虚拟机</b>