作者:京東科技 杜強強
前言
在Roma跨端方案中,JS虛擬機是框架的核心,負責執(zhí)行動態(tài)化的JS代碼。在Android平臺采用了基于V8的J2V8,iOS平臺則使用了系統(tǒng)自帶的JSCore,而在HarmonyOS中,由于業(yè)界無類似的框架,我們需要自行實現(xiàn)以確保核心基礎能力的完整。 鴻蒙虛擬機的開發(fā)經(jīng)歷了從最初 ArkTs2V8 到JSVM + Roma新架構方案。在此過程中,我們實現(xiàn)了完整的鴻蒙版的“J2V8”和 基于系統(tǒng)JSVM的JS虛擬機框架,解決了JS引擎庫移植、多語言通信能力、多類型數(shù)據(jù)結構轉換等眾多挑戰(zhàn)。本文將從實現(xiàn)的各個階段過程出發(fā),探討在實踐中遇到的問題及解決方案。
一、鴻蒙版 “J2V8”虛擬機實現(xiàn) - ArkTs2V8
ArkTs2V8框架依賴V8引擎, 鴻蒙前期交叉編譯資料少,V8官方也未有HarmonyOS端編譯方式。因此在這過程中, 我們采取初期使用QuickJS引擎(C語言開發(fā),代碼少,移植方便), 后期自編譯V8完成后替換QuickJS, 保證快速驗證跨端前期技術調(diào)研方案以及其他依賴項基礎能力的開展。 自編譯V8 通過學習交叉編譯相關技術,摸索式逐步解決編譯期間這種報錯,完成V8虛擬機移植。
ArkTs2V8 架構借鑒了Android J2V8(動態(tài)化-J2V8文章中講述了具體原理及實踐)的實現(xiàn)原理。 J2V8為針對V8的 Java實現(xiàn),采用最直接的方式在Java中訪問V8原始值,因此具備較高的性能。 在HarmonyOS中,采用V8作為JS引擎, JSI作為通信層完成設計。

1、引入JSI
考慮到跨端框架的未來發(fā)展,雖然通過C++ 能夠直接與V8交互,但這種方式不利于虛擬機代碼的共享和擴展。因此Roma框架引入JSI,以增強代碼的可擴展性,促進更有效的代碼共享,并實現(xiàn)更靈活的虛擬機集成。
JSI(JavaScript Interface),輕量級,通用且同步的JavaScript接口, 通過JSI,JS代碼可以直接與C++原生代碼通信。
有了JSI層對虛擬機的封裝,Roma框架開發(fā)者無需在關心虛擬機底層能力, 同時也可以自由切換引擎,比如使用V8,QuickJS、JSVM等, 規(guī)范了數(shù)據(jù)格式,統(tǒng)一為JSIValue。
2、API與框架設計原理
接口設計采用和J2V8 類似的設計,支持多虛擬機實例方式。

實現(xiàn)原理:
1、本地接口: 使用 napi 使用創(chuàng)建橋梁, 完成本地代碼調(diào)用Quick引擎函數(shù)。
2、C++數(shù)據(jù)綁定:在C++層面 ,定義虛擬機交互操作的相關函數(shù),完成V8引擎相關API 來執(zhí)行JS代碼、 處理JS對象和執(zhí)行虛擬機相關的操作。
3、JSIRuntime: 在C++層面引入JSI概念,通過完成JSIRuntime - QuickJSRuntime & V8Runtime, 完成虛擬機層通信能力。
4、虛擬機對象的定義及封裝:根據(jù)JS數(shù)據(jù)類型,定義ArkTS數(shù)據(jù)結構,包括基本數(shù)據(jù)類型、JSObject、JSArray、JSFunction。ArkTS側 類型對象持有C++ JSIValue 對象指針,當執(zhí)行具體能力時,通過napi 傳遞指針,完成具體功能的調(diào)用。 簡單來說,相當于ArkTS JS對象代理C++ 虛擬機數(shù)據(jù)對象。
5、內(nèi)存管理: ArkTs2V8負責管理ArkTS與JSValue 之間的內(nèi)存交互。其中C++側完成JSValue對象的創(chuàng)建、引用持有與銷毀。 ArkTS數(shù)據(jù)對象中定義對象釋放函數(shù), 數(shù)據(jù)使用完后,由ArkTS調(diào)用釋放內(nèi)存。
ArkTs2V8架構設計支持虛擬機多實例, 單個虛擬機的創(chuàng)建過程時由 ArkTs通過JSEngine發(fā)起創(chuàng)建JSRuntime虛擬機實例創(chuàng)建,經(jīng)過napi,在C++環(huán)境創(chuàng)建JSRuntime引擎實例及引用, 并完成環(huán)境Context及global的初始化, 同時創(chuàng)建ArkTs JSRuntime對象,代理C++虛擬機對象JSRuntime(QuickJSRuntime or V8Runtime) 并綁定指針引用。
初始化過程:

V8Runtime實現(xiàn)

3、JS、JSI、JSRuntime 關系

JSRuntime (QuickJSRuntime or V8Runtime) 是 JS運行時環(huán)境。一個 JSRuntime 通常包括一個或多個引擎,JSI 可以看作是連接 JS 代碼和 JSRuntime 的橋梁。通過 JSI,開發(fā)者可以更直接地與 JSRuntime 交互,實現(xiàn)原生功能的調(diào)用和管理。
4、部分過程剖析
ArkTs2V8實現(xiàn)的過程中,最基礎的兩個功能原理:JSObject對象的創(chuàng)建與獲取、原生方法的注入, 這兩個能力的實現(xiàn)可以擴展到其他大多數(shù)API功能實現(xiàn)上。
1、JSObject對象及獲取對象數(shù)據(jù)過程。
通過JSRuntime 發(fā)起接口的調(diào)用,通過napi,根據(jù)對象類型在C++側創(chuàng)建對象的JSValue對象及象指針引用, 并將引用指針綁定至ArkTS對象,完成對象的創(chuàng)建。

2、 JS虛擬機注入原生方法
ArkTS方法到JS虛擬機中,主要實現(xiàn)原理:
將ArkTs的方法 和 目標注冊對象指針 生成MethodDescriptor方法描述對象, 通過functionID將對象存儲在當前JSContext環(huán)境中。 通過napi 發(fā)起在C++側代理函數(shù)HostFunction的創(chuàng)建,并綁定ArkTs的方法的引用。 進入到JSI內(nèi)部,創(chuàng)建方法代理HostFunctionProxy 對象,綁定代理方法HostFunction及守護函數(shù)Finalizer, v8::External 將HostFunctionProxy與 JS環(huán)境對象(V8對象) 關聯(lián)起來,生成V8 Function , 此時V8函數(shù)會與HostFunctionProxy生命周期綁定。 簡單來說相當于ArkTS callback,傳遞至C++,C++創(chuàng)建JSI Callback并綁定ArkTS callback, JSI Callback 設置到HostFunctionProxy中,HostFunctionProxy 通過 v8::External與 JS環(huán)境綁定。
當JS觸發(fā)該該函數(shù)時,通過v8::External綁定HostFunctionProxy這層關系,HostFunctionProxy中JSI Callback會收到JS環(huán)境的響應消息,在通過綁定的ArkTs的方法 通過napi接口返回至ArkTS中,最終ArkTS收到方法響應。
這種代理函數(shù)的實現(xiàn), 初次學習可能比較復雜,但整個過程實際是多個對象間引用的持久化和不同數(shù)據(jù)對象的交換, 大致過程圖如下:

4、問題及挑戰(zhàn)
1、 數(shù)據(jù)對象的內(nèi)存管理
手動內(nèi)存管理。 ArkTs2V8 負責管理ArkTS與V8之間的內(nèi)存交互中,ArkTs發(fā)起對象的創(chuàng)建和銷毀。 整個內(nèi)存的管理是基于手動管理,需使用方用完后及時關閉,避免內(nèi)存泄露。 這種設計模式下,使用者操作不當極為容易造成內(nèi)存泄露,并且使用也較為不便。
針對這問題,在后續(xù)的迭代設計中,將內(nèi)存管理升級為自動內(nèi)存管理的方式。 JS為單線程執(zhí)行,單方法片段或一些邏輯中,如果有了調(diào)用開始時機和結束調(diào)用時機, 通過開始時記錄當前時刻后開始創(chuàng)建的對象,在調(diào)用結束時刻對記錄的對象進行統(tǒng)一的內(nèi)存釋放,類似于標記垃圾回收,完成內(nèi)存的統(tǒng)一管理。借助Roma框架中對虛擬機層的封裝,做到了內(nèi)存自動管理。
2、 跨語言性能問題
基于ArkTs2V8 的API實現(xiàn),在原生、JS環(huán)境中,無法直接使用對方的數(shù)據(jù)類型,二者之間數(shù)據(jù)類型需要轉換。 JS到原生的過程中,ArkTs2V8中目前提供的API僅可以獲取當前層級的JS對象數(shù)據(jù),子對象數(shù)據(jù)需要通過遞歸遍歷從JS環(huán)境中一一獲取。因此解析的過程中需要頻繁的通過C++讀取V8,當數(shù)據(jù)量較大時通常比較耗時。拿常用的網(wǎng)絡模塊來說,接口下發(fā)的業(yè)務接口數(shù)據(jù)至少都在幾K甚至幾十K,轉為JS對象在中端性能手機上 會有幾十ms的耗時,這對單線程模式的JS環(huán)境來說影響時巨大的。
ArkTS 、C++ 跨語言通信性能我們可以采取類似于Roma Android的通信次數(shù)壓縮策略,或者使用JSON序列化來減少跨語言交互的性能損耗, 但無論用哪種都僅是從行為上規(guī)避跨語言的性能,而無法徹底解決。
3、 線程管理問題
ArkTS基于TS語言,由于語言特性,ArkTS線程隔離,那么對于ArkTs2V8這種接口設計并不友好。 JS線程需要在ArkTS開啟獨立worker JS線程,收發(fā)JS消息,線程間的隔離,涉及再次序列化數(shù)據(jù)影響性能。
基于問題2、3 以及對框架未來的思考,Roma鴻蒙端決定采用新的方案: 框架C++化,框架邏輯實現(xiàn)全部放在native側, 虛擬機實現(xiàn)全部切C++,C++側完成線程管理,ArkTS不在承擔線程和邏輯任務。 這種既提升了解決了問題,提升框架性能,也為今后框架移植其他平臺打好基礎。
二、基于JSVM虛擬機實現(xiàn) (Roma新架構)
1、鴻蒙JSVM
在V8移植上,從短期看雖然我們初步掌握了V8交叉編譯移植技術,但從穩(wěn)定性、兼容性、維護成本、包大小等維度看, 采用系統(tǒng)內(nèi)置虛擬機有巨大的長期收益。 年初Roma框架與華為專家多次溝通交流,最終HarmonyOS將V8內(nèi)置到了操作系統(tǒng), Q2我們實現(xiàn)了第三個JSRuntime - JSMVRuntime, 至此鴻蒙動態(tài)化架構修改趨于穩(wěn)定。
JSVMRuntime:

2、新架構思路 - Roma架構C++化
新架構的設計思路SDK核心邏輯整體C++側實現(xiàn), 這樣在底層引擎與核心流程之間可以直接c++通信,線程間上與其他端保持相同 - 三線程模型JS線程 +UI線程 + 耗時計算線程。通過C++ PThread完成線程管理, 從而避免跨語言、ArkTS線程隔離帶來的多種性能損耗。 在數(shù)據(jù)結構設計上, JS數(shù)據(jù)采用JSI::Value, 與其他線程數(shù)據(jù)相互交互時, 統(tǒng)一使用folly完成。 另外將虛擬機層下沉,對外提供JSExecutor, 功能開發(fā)時框架開發(fā)者無需關心虛擬機層的實現(xiàn)。
虛擬機方法與對象的注入上, 通過HostObject代理對象能力的雙邊映射,原生模塊直接與JS 同步或異步交互, 從而縮短了流程鏈路。

框架大致原理:

3、過程遇到的
1、JSVM字符串引用問題
JSVMRuntime實現(xiàn)期間,字符串無法創(chuàng)建對象引用。 JSI的設計中將字符串作為 pointer 自定義指針類,通過指針地址訪問, 與其相同的還有對象,方法。 在許多語言中字符串都作為一種特殊的類型(非基本數(shù)據(jù)類型), 例如在C++中,字符是一種基本數(shù)據(jù)類型,但是字符串不是,字符串由字符組成, V8引擎亦如此。 V8中通常使用v8::String來創(chuàng)建JS字符串, 我們可以對齊進行持久化引用。
而JSVM中 OH_JSVM_CreateReference 無法針對字符串類型創(chuàng)建引用, 字符串的持久化需從JSVM_Value從copy出來通過智能指針或者new內(nèi)存的方式進行存儲,這種copy持久化的方式會造成字符串內(nèi)存兩份(JSVM一份,自己存一份), 實際開發(fā)中大量的字符串類型轉換,這樣會造成內(nèi)存占比過高。
為此, 經(jīng)過與華為專家多次交流溝通,最終將字符串歸為引用類型,可通過OH_JSVM_CreateReference持久化引用,修改后的方式如下:

2、HostObject代理對象實現(xiàn)
HostObject 是JS對象,提供與原生直接通信的方式。 相當于 native 在 JS的代理對象,雙向映射,原生模塊直接與JS 同步或異步交互, 在一些功能實現(xiàn)上可以縮短流程鏈路, 在JS中可直接調(diào)用C++的對象。 在動態(tài)化中, 模塊的實現(xiàn)采用的就是HostObject能力, 框架層實現(xiàn)模塊代理對象及橋通信層面的雙向通信過程。 比如登錄模塊,在ArkTS側封裝模塊的API,通過C側的HostObject映射,可以在JS中直接調(diào)用登錄模塊的登錄,退出登錄等能力。 HostObject的實現(xiàn),雖然在框架層面相比于喬通道的方式更加復雜,但對于復雜邏輯流程和交互鏈路, 基礎開發(fā)可以更注重于功能邏輯。
HostObject 實現(xiàn)過程較為復雜, 但我們可以將過程拆分,通過對象管理 + 代理函數(shù)的方式將過程簡化。 首先對象的管理直接JSRuntime中持久化即可,Roma中采用智能指針,那么就剩下代理函數(shù),前面我們講了JS中注入方法里面包括了代理函數(shù)的實現(xiàn)原理,采用類似的思路來完成HostObject。
HarmonyOS提供的JSVM API最初僅支持代理函數(shù)的創(chuàng)建, 而我們需要是創(chuàng)建代理對象,對象中可以有任意方法,僅通過代理函數(shù)方式無法滿足任意方法的需求,為此通過在JS中注入代理對象腳本實現(xiàn),通過Proxy代理的方式,將get、set等代理對象的方法通過代理函數(shù)的方式返回,這種情況下,我們的函數(shù)數(shù)量就被簡化成了get、set及一些固定的方法。 通過這些方法做代理轉接,調(diào)用到C++對象方法,借助JSI::Value的包裝,將具體結果返回。
JS代理腳本部分代碼:

大致實現(xiàn)過程:

示例 - 基于HostObject Console能力實現(xiàn)

三、總結
0到1實現(xiàn)鴻蒙版“j2v8”、“JSRuntime” 讓我們更加了解引擎實現(xiàn)中的各種細節(jié)和一些難點問題的解決。 一些方案的實現(xiàn),也可以延展到其他(非虛擬機)場景。 Roma 框架C++, 讓Roma框架走向技術深水區(qū), 為今后capi、未來技術做好了基礎,旨在帶來更優(yōu)的性能和更好的用戶體驗。
審核編輯 黃宇
-
JS
+關注
關注
0文章
79瀏覽量
18988 -
架構
+關注
關注
1文章
532瀏覽量
26589 -
虛擬機
+關注
關注
1文章
972瀏覽量
30464 -
鴻蒙
+關注
關注
60文章
2963瀏覽量
45883 -
HarmonyOS
+關注
關注
80文章
2153瀏覽量
36037
發(fā)布評論請先 登錄
虛擬機數(shù)據(jù)恢復—虛擬機vmdk文件被刪除的數(shù)據(jù)恢復案例
Arm架構助力Azure Cobalt 100虛擬機工作負載性能提升
開源鴻蒙技術大會2025丨虛擬化與容器分論壇:構筑開源鴻蒙虛擬化技術高地
搭建自己的ubuntu系統(tǒng)之創(chuàng)建ubuntu虛擬機
Taro on HarmonyOS 技術架構深度解析
【HarmonyOS 5】金融應用開發(fā)鴻蒙組件實踐
中軟國際鴻云虛擬機安裝指南
迅為RK3588開發(fā)板掛載Windows以及虛擬機Ubuntu測試
ArkUI-X跨平臺技術落地-華為運動健康(一)
鴻蒙5開發(fā)寶藏案例分享---跨線程性能優(yōu)化指南
鴻蒙5開發(fā)寶藏案例分享---折疊屏開發(fā)實踐
TECS OpenStack資源池虛擬機網(wǎng)絡二層地址無法互通的問題處理
鴻蒙跨端實踐-JS虛擬機架構實現(xiàn)
評論