自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

揭秘:支付寶小程序 V8 Worker 技術(shù)演進(jìn)

開發(fā) 開發(fā)工具
本文分享支付寶小程序 V8 Worker 相關(guān)工作沉淀和總結(jié),包括技術(shù)演進(jìn)、基礎(chǔ)架構(gòu)、基礎(chǔ)功能、以及 JS 引擎能力輸出,以及一些優(yōu)化方案等。歡迎同學(xué)們共同探討,指正。

本文分享支付寶小程序 V8 Worker 相關(guān)工作沉淀和總結(jié),包括技術(shù)演進(jìn)、基礎(chǔ)架構(gòu)、基礎(chǔ)功能、以及 JS 引擎能力輸出,以及一些優(yōu)化方案等。歡迎同學(xué)們共同探討,指正。

從 Service Worker 到 V8 Worker

本節(jié)簡要介紹支付寶小程序從 Service Worker 到 V8 Worker 的技術(shù)演進(jìn)過程。

眾所周知,支付寶小程序源碼打包完成之后主要分為兩部分:

  • 第一部分負(fù)責(zé)小程序的視圖展示,打包產(chǎn)物為 index.js,我們稱為 Render 部分
  • 第二部分負(fù)責(zé)小程序的業(yè)務(wù)邏輯、視圖更新等,打包產(chǎn)物為 index.worker.js,我們稱為 Worker 部分

同時(shí),前端框架 APPX 也分為 Render 部分(af-appx.min.js)和 Worker 部分(af-appx.worker.min.js):

  • Render部分(index.js 和 af-appx.min.js)運(yùn)行在 UCWebView 或 SystemWebView 上
  • Worker 部分(index.worker.js 和 af-appx.worker.min.js)運(yùn)行于 Service Worker[1] 上

Service Worker

Service Worker 由瀏覽器內(nèi)核提供,設(shè)計(jì)目的是用于充當(dāng) Web 應(yīng)用程序與瀏覽器之間的代理服務(wù)器;Service Worker 運(yùn)行在獨(dú)立的 worker 上下文,因此它不能訪問 DOM。相對于驅(qū)動(dòng)應(yīng)用的主 JavaScript 線程,它運(yùn)行在其他線程中,所以不會(huì)造成阻塞。

??

??

 

但是有一個(gè)問題是 Service Worker 的啟動(dòng)和 Render 部分的啟動(dòng)是串行的,必須是在 WebView 啟動(dòng)之后,由 Render 部分的 JS 發(fā)起。這對小程序來說就是較大的性能瓶頸。

??

??

 

WebView Worker

為了解決 Worker 和 Render 串行初始化和執(zhí)行帶來的性能問題,小程序團(tuán)隊(duì)嘗試過使用 WebView 來執(zhí)行 Worker。也就是在啟動(dòng)小程序的時(shí)候同時(shí) new 出兩個(gè) WebView,一個(gè) WebView 用來渲染 Render 部分,另一個(gè) WebView 專門用來執(zhí)行 Worker 部分的 JS 腳本。但是專門使用一個(gè) WebView 來執(zhí)行 Worker 部分的 JS 腳本,無疑是”大材小用“,使用一個(gè) WebView 的資源消耗必然是較高的。

??

??

 

V8 Worker

Service Worker 的串行初始化會(huì)影響小程序啟動(dòng)性能,WebView Worker 來運(yùn)行小程序 Worker 代碼又不夠輕量,使用專有 JS 引擎來做 Worker 部分的工作乃是最優(yōu)選擇,因此 V8 Worker 應(yīng)運(yùn)而生。

下圖是小程序 V8 Worker 的基本結(jié)構(gòu),本文后面繼續(xù)詳細(xì)描述。

??

??

 

利用 V8 引擎運(yùn)行 Worker 主要有以下一些優(yōu)勢:

  • 能夠解決 Render 和 Worker 串行初始化和運(yùn)行的問題,WebView 和 V8 引擎可并行初始化、可并行執(zhí)行 Render 和 Worker 部分的 JS 腳本
  • 能夠提供 JS 安全運(yùn)行環(huán)境,隔離框架 JS 和業(yè)務(wù) JS
  • 易于給小程序注入 JS 對象,綁定 JSAPI
  • 能夠支持更豐富的數(shù)據(jù)類型,如 ArrayBuffer 等
  • 能夠擴(kuò)展 Worker 能力,提供小程序插件、多線程 Worker 等功能
  • 能夠充分利用 V8 引擎的能力做性能優(yōu)化,如 V8 CodeCache 等
  • 能夠給小程序以外的業(yè)務(wù)提供 JS 引擎能力,如 V8 Native 插件
  • 能夠自定義 JS 引擎運(yùn)行參數(shù)

??

??

 

V8 Worker 基礎(chǔ)架構(gòu)

本節(jié)主要介紹了支付寶小程序的 V8 Worker 工程結(jié)構(gòu)、基于 V8 Worker 的小程序架構(gòu);同時(shí)如果對 V8 引擎不是很熟悉,這里給出了 V8 的簡要介紹和學(xué)習(xí)資料鏈接。

V8 簡介與入門

在介紹 V8 Worker 之前,先簡要了解下 V8 引擎[2]本身。如果對 V8 很熟的大牛請自行跳過。

V8 是 Google 的開源項(xiàng)目,是一個(gè)高性能 JavaScript 和 WebAssembly 引擎,應(yīng)用于用于 Chrome 瀏覽器、Node.js 等項(xiàng)目。學(xué)習(xí) V8 的門檻還是比較高,這里只給出了閱讀本文所需要知道的 V8 基本概念,以及官方的嵌入式 V8 的 HelloWorld 代碼,同時(shí)給出一些學(xué)習(xí)鏈接。

嵌入式 V8 基本概念

1 Isolate (隔離)

Isolate 和操作系統(tǒng)中進(jìn)程的概念有些類似。進(jìn)程是完全相互隔離的,一個(gè)進(jìn)程里有多個(gè)線程,同時(shí)各個(gè)進(jìn)程之間并不相互共享資源。Isolate 也是一樣,Isolate1 和 Isolate2 兩個(gè)擁有各自堆棧的虛擬機(jī)實(shí)例,且相互完全隔離。

2 Contexts (上下文)

在 V8 中,一個(gè) context 就是一個(gè)執(zhí)行環(huán)境, 它使得可以在一個(gè) V8 實(shí)例中運(yùn)行相互隔離且無關(guān)的 JavaScript 代碼。你必須為你將要執(zhí)行的 JavaScript 代碼顯式的指定一個(gè) context。

之所以這樣是因?yàn)?JavaScript 提供了一些內(nèi)建的工具函數(shù)和對象,他們可以被 JS 代碼所修改。比如,如果兩個(gè)完全無關(guān)的 JS 函數(shù)都在用同樣的方式修改一個(gè) global 對象,很可能就會(huì)出現(xiàn)一個(gè)意外的結(jié)果。

3 Handle(句柄)與 垃圾回收

Handle 提供了一個(gè) JS 對象在堆內(nèi)存中的地址的引用。V8 垃圾回收器將回收一個(gè)已無法被訪問到的對象占用的堆內(nèi)存空間。垃圾回收過程中,回收器通常會(huì)將對象在堆內(nèi)存中進(jìn)行移動(dòng). 當(dāng)回收器移動(dòng)對象的同時(shí),也會(huì)將所有相應(yīng)的 Handle 更新為新的地址。

當(dāng)一個(gè)對象在 JavaScript 中無法被訪問到,并且也沒有任何 Handle 引用它,則這個(gè)對象將被當(dāng)作 "垃圾" 對待?;厥掌鲗⒉粩鄬⑺信卸? "垃圾" 的對象從堆內(nèi)存中移除。V8 的垃圾回收機(jī)制是其性能的關(guān)鍵所在。

Local Handles 保存在一個(gè)棧結(jié)構(gòu)中,當(dāng)棧的析構(gòu)函數(shù)(destructor)被調(diào)用時(shí)將同時(shí)被銷毀。這些 handle 的生命周期取決于 handle scope(當(dāng)一個(gè)函數(shù)被調(diào)用的時(shí)候,對應(yīng)的 handle scope 將被創(chuàng)建)。當(dāng)一個(gè) handle scope 被銷毀時(shí),如果在它當(dāng)中的 handle 所引用的對象已無法再被 JavaScript 訪問,或者沒有其他的 handle 指向它,那么這些對象都將在 scope 的銷毀過程中被垃圾回收器回收。入門指南中的例子使用的就是這種 Handle。

Persistent handle 是一個(gè)堆內(nèi)存上分配的 JavaScript 對象的引用,這點(diǎn)和 local handle 一樣。但它有兩個(gè)自己的特點(diǎn),是對于它們所關(guān)聯(lián)的引用的生命周期管理方面。當(dāng)你希望持有一個(gè)對象的引用,并且超出該函數(shù)調(diào)用的時(shí)期或范圍時(shí),或者是該引用的生命周期與 C++ 的作用域不一致時(shí),就需要使用 persistent handle 了。例如 Google Chrome 就是使用 persistent handle 引用 DOM 節(jié)點(diǎn)。Persistent handle 支持弱引用,即 PersistentBase::SetWeak,它可以在其引用的對象只剩下弱引用的時(shí)候,由垃圾回收器出發(fā)一個(gè)回調(diào)。

4 Templates(模板)

在一個(gè) context 中,template 是 JavaScript 函數(shù)和對象的一個(gè)模型。你可以使用 template 來將 C++ 函數(shù)和數(shù)據(jù)結(jié)構(gòu)封裝在一個(gè) JavaScript 對象中,這樣它就可以被 JS 代碼操作。例如,Chrome 使用 template 將 C++ DOM 節(jié)點(diǎn)封裝成 JS 對象,并且將函數(shù)安裝在 global 命名空間中。你可以創(chuàng)建一個(gè) template 集合, 在每個(gè)創(chuàng)建的 context 中你都可以重復(fù)使用它們。你可以按照你的需求,創(chuàng)建任意多的 template。然而在任意一個(gè) context 中,任意 template 都只能擁有一個(gè)實(shí)例。

在 JS 中,函數(shù)和對象之間有很強(qiáng)的二元性。在 C++ 或 Java 中創(chuàng)建一種新的對象類型通常要定義一個(gè)類。而在 JS 中你卻要?jiǎng)?chuàng)建一個(gè)函數(shù), 并以函數(shù)為構(gòu)造器生成對象實(shí)例。JS 對象的內(nèi)部結(jié)構(gòu)和功能很大程度上是由構(gòu)造它的函數(shù)決定的。這些也反映在 V8 的 template 的設(shè)計(jì)中, 因此 V8 有兩種類型的 template:

1)FunctionTemplate

一個(gè) Function Template 就是一個(gè) JS 函數(shù)的模型. 我們可以在我們指定的 context 下通過調(diào)用 template 的 GetFunction 方法來創(chuàng)建一個(gè) JS 函數(shù)的實(shí)例. 你也可以將一個(gè) C++ 回調(diào)與一個(gè)當(dāng) JS 函數(shù)實(shí)例執(zhí)行時(shí)被調(diào)用的 function template 關(guān)聯(lián)起來。

2)ObjectTemplate

每一個(gè) Function Template 都與一個(gè) Object Template 相關(guān)聯(lián)。它用來配置以該函數(shù)作為構(gòu)造器而創(chuàng)建的對象。

5 Accessors (存取器)

存取器是一個(gè)當(dāng)對象屬性被 JS 代碼訪問的時(shí)候計(jì)算并返回一個(gè)值的 C++ 回調(diào)。存取器是通過 Object Template 的 SetAccessor 方法進(jìn)行配置的。該方法接收屬性的名稱和與其相關(guān)聯(lián)的回調(diào)函數(shù),分別在 JS 讀取和寫入該屬性時(shí)觸發(fā)。

存取器的復(fù)雜性源于你所操作的數(shù)據(jù)的訪問方式:

  • 訪問靜態(tài)全局變量
  • 訪問動(dòng)態(tài)變量

6 Interceptors(攔截器)

我們可以設(shè)置一個(gè)回調(diào),讓它在對應(yīng)對象的任意屬性被訪問時(shí)都會(huì)被調(diào)用。這就是 Interceptor??紤]到效率,分為兩種不同的 interceptor:

  • 屬性名攔截器:當(dāng)通過字符串形式的屬性名訪問時(shí)調(diào)用。比如在瀏覽器中使用document.theFormName.elementName 進(jìn)行訪問。
  • 屬性索引攔截器:當(dāng)通過屬性的下標(biāo)/索引訪問時(shí)調(diào)用。比如在瀏覽器中使用document.forms.elements[0] 進(jìn)行訪問。

7 Security Model(安全模型)

在 V8 中,同源被定義為相同的 context。默認(rèn)情況下,是無法訪問別的 context 的。如果一定要這樣做,需要使用安全令牌或安全回調(diào)。安全令牌可以是任意值,但通常來說是個(gè)唯一的規(guī)范字符串。當(dāng)建立一個(gè) context 時(shí),我們可以通過 SetSecurityToken 來指定一個(gè)安全令牌, 否則 V8 將自動(dòng)為該 context 生成一個(gè)。

學(xué)習(xí)資料

官方文檔 [4]

  • Ignition: An Interpreter for V8 [5]
  • Ignition: Jump-starting an Interpreter for V8 [6]
  • V8: Hooking up the Ignition to the Turbofan [7]
  • V8 Code Caching [8]

基于 V8 Worker 的小程序架構(gòu)

本小節(jié)詳細(xì)講述 V8 Worker 的小程序架構(gòu),分別描述了 Render 部分和 V8 Worker 的 JSAPI 流程細(xì)節(jié),以及 Render 和 Worker 直接如何通信。

??

??

 

單 V8 Context 結(jié)構(gòu)

如上圖所示,在 V8 Worker 的初期,一個(gè)小程序占用一個(gè) V8 Isolate,一個(gè) V8 Isolate 只創(chuàng)建一個(gè) V8 Context。也就是小程序的前端框架 APPX 的代碼 appx.worker.min.js 和小程序的業(yè)務(wù)代碼 index.worker.js 運(yùn)行于同一個(gè) V8 Isolate 上的同一個(gè) V8 Context 上。這樣的設(shè)計(jì)就會(huì)存在 JS 安全性問題,業(yè)務(wù) JS 代碼可以通過拼接冒名的形式訪問到為 APPX 注入的內(nèi)部 JS 對象和內(nèi)部 JSAPI,在同一個(gè) V8 Context 中,是無法隔離開業(yè)務(wù) JS 代碼和 APPX 框架 JS 代碼的運(yùn)行環(huán)境的。后面我們會(huì)介紹如何解決這個(gè)安全問題。

Render 部分 JSAPI 流程

如上圖所示,Render 和 Nebula 直接的雙向通行是分別通過 Console.log 和 WebView 的 loadUrl[9] 接口進(jìn)行的。

容器到 Render

容器要加載運(yùn)行 Render 部分的 JS 腳本,都是通過 WebView 的 loadUrl 進(jìn)行;WebView 在運(yùn)行 Render 部分的 JS 腳本(af-appx.min.js 和 index.js)之前,需要提前注入 APPX 框架需要用到的全局 JS 對象,如 window.AlipayJSBridge[10] 等,供 JSAPI 調(diào)用使用。

Render 到容器

Render 側(cè)到容器的 JSAPI 的調(diào)用,本質(zhì)上是通過 Console.log[11] Web API 實(shí)現(xiàn)。

Worker 部分 JSAPI 流程

Worker 到容器

類似于 Render 部分,在初始化 V8 Worker 時(shí),也需要在 V8 Worker 環(huán)境中注入 AlipayJSBridge 這個(gè)全局 JS 對象,AlipayJSBridge 的定義在 workerjs_v8_origin.js [12]中,workerjs_v8_origin.js[13] 已提前在 V8 Worker 中加載。

AlipayJSBridge = { 
//xxxxx
call: function (func, param, callback) {
nativeFlushQueue(func, viewId, JSON.stringify(msg), extraData);
}
//xxxxx
}

同時(shí),我們已經(jīng)在 V8 Worker 環(huán)境中提前注入了 nativeFlushQueue API,同時(shí)綁定了這個(gè) API 的 JAVA 側(cè)回調(diào):

mV8Runtime.registerJavaMethod(new AsyncJsapiCallback(this), "__nativeFlushQueue__");

這樣 Worker 部分 JSAPI 通過 AlipayJSBridge.call() 調(diào)用,最終會(huì)回調(diào)到容器側(cè)的AsyncJsapiCallback() 。

容器到 Worker

JSAPI 在容器側(cè)處理完成之后,如果有返回結(jié)果,將會(huì)返回到 Worker。

Render 和 Worker 通信

基于容器總線的消息通道

以 Render 到 Worker 發(fā)送消息為例,流程大致為:

  • Render 側(cè)發(fā)送 postMessage 消息,此時(shí)消息需要經(jīng)過一次序列化轉(zhuǎn)成字符串。
  • WebChromeClient onConsoleMessage 攔截到消息,反序列化成 JSONObject 并發(fā)送到容器總線 bridge.sendToNative(event) 。
  • 容器總線進(jìn)行事件分發(fā)。
  • worker 插件攔截到 postMessage 事件,并發(fā)送到 worker。
  • V8Worker 將消息反序列化成 string,并轉(zhuǎn)成 JS 數(shù)據(jù)類型,傳到 Worker 所在的 V8Context。
  • workerjs_v8_origin.js 中_invokeJS 函數(shù)被調(diào)用,至此,Worker 已收到來自 Render 的消息。

基于 MessageChannel 的消息通道

可以看出,基于容器總線的消息通道,一個(gè)消息從 Render 到 Worker 中間需要經(jīng)過多次的序列化和反序列化,這是非常耗時(shí)的操作;不僅在小程序啟動(dòng)過程中影響小程序啟動(dòng)速度,小程序的滑動(dòng)等交互事件都會(huì)有大量的 Worker 和 Render 之間的消息傳遞,所以也會(huì)影響幀率。

于是,基于 MessageChannel 的消息通道應(yīng)運(yùn)而生。

MessageChannel 允許我們創(chuàng)建一個(gè)新的消息通道,并通過它的兩個(gè) messagePort 屬性發(fā)送數(shù)據(jù)。如下圖所示,MessageChannel 會(huì)創(chuàng)建一個(gè)管道,管道的兩端分別代表一個(gè) messagePort,都能夠通過 portMessage 向?qū)Ψ桨l(fā)送數(shù)據(jù),通過 onmessage 來接受對方發(fā)送過來的數(shù)據(jù)。利用 MessageChannel 的特性,render 和 worker 之間的通信可以不通過 Nebula 總線,這樣減少了消息的序列化和反序列化。

??

??

 

V8 Worker 接入 JSI

背景

??

??

 

隨著支付寶端以及整個(gè)集團(tuán)使用V8引擎的業(yè)務(wù)越來越多,對 V8 引擎的升級維護(hù)工作就越來越復(fù)雜和重要。每個(gè)業(yè)務(wù)可能使用不同的接口,升級 V8 引擎時(shí)都需要重新適配。同時(shí),剛才前文也提到了,目前 V8 引擎由 UCWebView 內(nèi)核提供,使用 V8 需要重新進(jìn)行拷貝。

如何解決這些問題呢?"計(jì)算機(jī)科學(xué)領(lǐng)域的任何問題都可以通過增加一個(gè)間接的中間層來解決",于是就誕生了 JSI(JavaScript Interface)。

JSI 簡介

JSI(JavaScript Interface)是對 JavaScript 引擎(V8、JSC 等)進(jìn)行封裝,給業(yè)務(wù)方提供基礎(chǔ)的、完整的、穩(wěn)定的、與具體 JS引擎無關(guān)的、向后兼容的 Java API 和 Native API。

??

??

 

JSI 帶來的優(yōu)勢有:

與具體 JavaScript 引擎無關(guān)的、各平臺通用 JSI API

實(shí)現(xiàn)了 Inspector、Code Cache、JS Timer 等通用的功能,能讓業(yè)務(wù)方更加集中精力地關(guān)注于業(yè)務(wù)方面的開發(fā),縮短開發(fā)周期

JSI 負(fù)責(zé)處理 JS 引擎版本兼容性問題,接入業(yè)務(wù)無感知

JSI 直接使用 UC 內(nèi)核中的 libwebviewuc.so,不需要進(jìn)行 V8 拷貝

JSI 采用支付寶和 UC 共建的形式,JSI 接入層下沉到 Ariver 工程,通過 Ariver 輸出到集團(tuán)各業(yè)務(wù)

基于 JSI 的 V8 Worker

下圖是基于 JSI 的 V8 Worker 工程結(jié)構(gòu)。對比基于 J2V8[14] 的 V8 Worker 發(fā)現(xiàn),小程序、小游戲、Cube 等業(yè)務(wù)只需要通過 JSI 的 Java 接口去加載 V8 引擎即可,JSI 中使用 U4 Linker 加載 libwebviewuc.so,可復(fù)用 UC WebView SDK 中的 libwebviewuc.so,且無需拷貝,解決了與 UC WebView 在同一個(gè)進(jìn)程中共存時(shí) libwebviewuc.so 全局變量沖突的問題。JSI 同時(shí)提供了 Java 和 C++ 兩種封裝 API,方便業(yè)務(wù)方接入。

JSI 接入文檔詳細(xì)介紹了如何快速通過 JSI 來使用 JS 引擎:

  • Java 和 Native 側(cè)的初始化
  • 創(chuàng)建 JSEngine(對應(yīng)于 v8::isolate)
  • 創(chuàng)建 JSContext(對應(yīng)于 v8::Context)
  • 如何通過 Java/C++ 接口注入 JS 對象(全局常量、全局函數(shù)、全局訪問器)
  • 如何執(zhí)行 JS 腳本
  • Trace 分析、Timer 等

??

??

 

V8 Worker 如何解決 JS 安全問題

前文已經(jīng)介紹,采用單 V8 Isolate 單 V8 Context 結(jié)構(gòu)的 V8 Worker 會(huì)存在 JS 安全問題,無法隔離業(yè)務(wù) JS 和前端框架 JS 的運(yùn)行環(huán)境。下面就介紹多 Context 隔離的 V8 Worker 和多 Isolate 隔離多線程 Worker。

多 Context 隔離

下圖描述了多 V8 Context 隔離架構(gòu)的 V8 Worker。對于同一個(gè)小程序,在同一個(gè) V8 Isolate 下,分別為小程序前端框架腳本(af-appx.worker.minjs)、小程序業(yè)務(wù)腳本(index.worker.js)和小程序插件[15]腳本(plugin/index.worker.js)創(chuàng)建單獨(dú) APPX Context、Biz Context、Plugin Context(jsi::JSContext 就對應(yīng)于 v8::Context)。同一個(gè)小程序可能會(huì)存在多個(gè)小程序插件,對于每一個(gè)插件都會(huì)分配一個(gè)單獨(dú) V8 Context 運(yùn)行環(huán)境。

如 V8 Context 安全模型[16]所描述,同源即被定義為 Context,默認(rèn)情況下不同的 Context 是不能相互訪問的,除非通過 SetSecurityToken 設(shè)定安全令牌。正式利用了這一特性,我們將前端框架、小程序業(yè)務(wù)和小程序插件的 JS 運(yùn)行環(huán)境進(jìn)行了安全隔離。

??

??

 

多 Isolate 隔離的多線程 Worker

在小程序中,對于一些異步處理的任務(wù),可以放置于后臺 Worker 線程去運(yùn)行,待運(yùn)行結(jié)束后,再把結(jié)果返回到小程序主線程,這就是多線程 Worker。

??

??

 

上圖描述了多線程 Worker 的設(shè)計(jì)框架。小程序 Worker 主線程運(yùn)行于單獨(dú)的 V8 Isolate 上,同時(shí),業(yè)務(wù) JS、APPX 框架 JS、插件 JS 會(huì)運(yùn)行屬于各自的 V8 Context 上。同時(shí)對于每一個(gè) Worker 任務(wù),都會(huì)單獨(dú)起一個(gè) Worker 線程,創(chuàng)建單獨(dú)的 V8 Isolate 和 V8 Context 實(shí)例。每一個(gè) Worker 任務(wù)和小程序主線程中的任務(wù)都是相互線程隔離的、Isolate 隔離的。

Isolate 隔離意味著 V8 堆的隔離,因此 Worker 主線程和后臺 Worker 線程,是無法直接傳遞數(shù)據(jù)的。Worker 主線程和后臺 Worker 線程要想實(shí)現(xiàn)數(shù)據(jù)傳遞,則需要進(jìn)行序列化和反序列化(Serialize 和 Deserialize)。序列化即將數(shù)據(jù)從源 V8 堆上拷貝至 C++ 堆上,反序列化即將數(shù)據(jù)從 C++ 堆上拷貝至目標(biāo) V8 堆上。Worker 主線程和后臺 Worker 線程通過序列化和反序列化的接口 postMessage 和 onMessage 來進(jìn)行數(shù)據(jù)傳遞。

JS 引擎能力輸出

支付寶中一些其他業(yè)務(wù)如(Native GCanas)想要在 C++ 層獲得 JS 引擎能力,同時(shí)不想自己費(fèi)力去重新接入 JS 引擎。這時(shí)需要 V8 Worker 具備將小程序的 JS 運(yùn)行環(huán)境對外輸出的能力。V8 Native 插件是其中一個(gè)方案。

V8 Native 插件

下圖描述了 V8 Native 插件的框架。設(shè)計(jì)思路如下:

在 V8 Worker 中增加一層 C++ 插件代碼,定義 Native 插件的接口,加載業(yè)務(wù)的動(dòng)態(tài)鏈接庫并管理插件。

將小程序 JS 運(yùn)行環(huán)境(基于 JSI 的 C++ 接口,jsi::JSEngine、jsi::JSContext)通過插件接口暴露給插件業(yè)務(wù)方,業(yè)務(wù)方即可獲得小程序JS運(yùn)行環(huán)境,方便添加自定義的 JS 對象,綁定自定義 JSAPI。

  • V8 Worker 將小程序生命周期事件,通過插件接口通知給業(yè)務(wù)方。
  • 同時(shí)給插件業(yè)務(wù)暴露 PostTask 接口,允許插件業(yè)務(wù)將任務(wù)放到小程序的 JS 線程去執(zhí)行。

插件業(yè)務(wù)通過接入 V8 Native 插件將獲得如下能力:

  • 獲得小程序生命周期事件
  • 獲得小程序 JS 執(zhí)行環(huán)境
  • 在小程序 JS 線程執(zhí)行任務(wù)
  • 訪問小程序的 JS 對象,JSAPI
  • 注入自定義 JS 對象,綁定自定義的 C++ 實(shí)現(xiàn) JSAPI

由于插件業(yè)務(wù)能夠直接獲得小程序 JS 的執(zhí)行環(huán)境,因此插件業(yè)務(wù)必須可信的,否則會(huì)帶來安全問題;所以在 V8 Worker java 層需要對插件進(jìn)行白名單管理和開關(guān)控制。

??

??

 

V8 Worker 性能優(yōu)化

并行初始化

V8 Worker 最初引入的原因就是為了解決小程序 Render 和 Worker 串行初始化和執(zhí)行的問題。前文已經(jīng)介紹,這里不再贅述。

Code Caching

??

??

 

上圖是 V8 code caching 的原理。因?yàn)?JS 是 JIT 語言,所以 V8 運(yùn)行 JS 時(shí)需要先解析和編譯,因此 JS 的執(zhí)行效率一直都是個(gè)問題。V8 code caching 的原理是,第一次運(yùn)行 JS 腳本的時(shí)候同時(shí)會(huì)生成該 JS 腳本的字節(jié)碼緩存,并保存在本地磁盤,第二次再運(yùn)行同一個(gè)腳本的時(shí)候,V8 可以利用第一次保存的字節(jié)碼緩存重建 Compile 結(jié)果,這樣就不需要重新 CompileCode。這樣第二次利用 Code Cache 之后,執(zhí)行這個(gè)腳本將會(huì)更快。

V8 Code caching 分為兩種:

  • Lazy Code caching:只將跑過的熱函數(shù)生成 code caching
  • Eager Code caching:將整個(gè)JS腳本都生成 code caching

Eager Code caching 生成的緩存將會(huì)更全,熱點(diǎn)函數(shù)命中率也會(huì)更高。同時(shí)體積將會(huì)更大,因此第二次從磁盤加載緩存時(shí)耗時(shí)也會(huì)更多。V8 官方宣稱 Eager Code caching會(huì)比 Lazy Code caching 減少 20%-40% 的 parse 和 compile 的時(shí)間。實(shí)際上我們通過實(shí)驗(yàn)發(fā)現(xiàn) Eager Code caching 并不比 UC 目前的 Lazy Code caching 有更好的效果。原因是緩存的體積對性能影響巨大。但是通過 Trace 分析,使用 Eager Code caching 和沒有使用 cache 相比,JS 執(zhí)行時(shí)間還是有較大的提升。

??

??

 

相關(guān)鏈接

[1]https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API[2]https://v8.dev/docs[3]https://chromium.googlesource.com/v8/v8/+/branch-heads/6.8/samples/hello-world.cc

[4]https://v8.dev/docs

[5]https://docs.google.com/presentation/d/1OqjVqRhtwlKeKfvMdX6HaCIu9wpZsrzqpIVIwQSuiXQ/edit#slide=id.ge4ef702cb_2_67

[6]https://docs.google.com/presentation/d/1HgDDXBYqCJNasBKBDf9szap1j4q4wnSHhOYpaNy5mHU/edit#slide=id.g1357e6d1a4_0_58

[7]https://docs.google.com/presentation/d/1HgDDXBYqCJNasBKBDf9szap1j4q4wnSHhOYpaNy5mHU/edit#slide=id.g1357e6d1a4_0_58

[8]https://v8.dev/blog/code-caching

[9]https://developer.android.com/reference/android/webkit/WebView#loadUrl(java.lang.String)

[10]https://codesearch.alipay.com/source/xref/Android_wallet_master/android-phone-nebula-git/nebula/js/h5_bridge.js?r=78c30345[11]https://developer.mozilla.org/en-US/docs/Web/API/Console/log[12]https://codesearch.alipay.com/source/xref/Android_wallet_master/android-ariver/js/workerjs_v8_origin.js?r=b59d7f92[13]https://codesearch.alipay.com/source/xref/Android_wallet_master/android-ariver/js/workerjs_v8_origin.js?r=b59d7f92[14]https://github.com/eclipsesource/J2V8[15]https://opendocs.alipay.com/mini/plugin/plugin-introduction[16]https://v8.dev/docs/embed#security-model

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2019-11-13 09:46:08

技術(shù)研發(fā)指標(biāo)

2017-09-28 14:48:46

支付寶深度學(xué)習(xí)xNN

2012-11-21 17:37:46

Oracle技術(shù)嘉年華

2019-02-27 09:45:53

小程序開發(fā)者支付寶

2020-09-02 12:15:27

支付寶交警移動(dòng)應(yīng)用

2021-09-09 15:30:28

鴻蒙HarmonyOS應(yīng)用

2014-11-17 10:52:56

支付寶去阿里化

2021-01-25 14:13:26

iOS支付寶支付

2025-01-17 07:48:45

支付寶程序員技術(shù)圈

2020-07-14 16:18:55

支付寶微信小程序

2022-01-07 10:28:49

支付寶體驗(yàn)科技

2018-03-27 12:02:31

央行支付寶紅包

2009-09-17 12:15:28

互聯(lián)網(wǎng)

2011-04-21 11:27:42

Firefox支付寶

2017-11-21 17:21:42

WOT峰會(huì)

2018-04-11 09:24:50

手機(jī)支付寶小程序

2009-11-23 10:02:22

PHP支付寶接口

2009-12-14 16:31:00

Linux安裝支付寶

2017-12-18 18:23:09

支付寶掃碼賺錢支付寶套路
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號