日本免费全黄少妇一区二区三区-高清无码一区二区三区四区-欧美中文字幕日韩在线观看-国产福利诱惑在线网站-国产中文字幕一区在线-亚洲欧美精品日韩一区-久久国产精品国产精品国产-国产精久久久久久一区二区三区-欧美亚洲国产精品久久久久

鴻蒙跨端實(shí)踐-揭秘視圖渲染流程

鴻蒙跨端實(shí)踐-揭秘視圖渲染流程

文章圖片

鴻蒙跨端實(shí)踐-揭秘視圖渲染流程

文章圖片

鴻蒙跨端實(shí)踐-揭秘視圖渲染流程

文章圖片

鴻蒙跨端實(shí)踐-揭秘視圖渲染流程
作者:京東科技 劉寧
一、前言

通過本文你將全面清晰的洞悉動態(tài)化跨端的實(shí)現(xiàn)原理 , 感受黑悟空(數(shù)據(jù))一路打怪升級(在不同語言環(huán)境中流轉(zhuǎn)改造) , 逆天改命(操控原生視圖繪制) , 終成齊天大圣(完成視圖渲染呈現(xiàn))的艱辛歷程 。
二、原理介紹1.動態(tài)化跨端原理介紹動態(tài)化- 羅碼(Roma , 后文統(tǒng)稱動態(tài)化)是一個完全自主研發(fā)的一站式跨平臺解決方案 , 一份代碼 , 可以在 Android、iOS、Harmony 及 Web 上運(yùn)行 。 動態(tài)化的跨端原理與 React Native 、Weex 一致 , 在吸取業(yè)界各跨端框架優(yōu)勢的基礎(chǔ)上 , 加上自主創(chuàng)新 , 打造的一個完全自主可控的綜合跨端解決方案 。 其跨端的理論基礎(chǔ)在于各端都具備統(tǒng)一解析和執(zhí)行 JavaScript 的能力(JS 虛擬機(jī)) 。 業(yè)務(wù)代碼被打包成 js 文件 , 在各端被 JS 虛擬機(jī)加載 , 被解析成多條不同功能的指令 , 調(diào)用原生宿主能力 , 完成數(shù)據(jù)的傳遞和視圖的繪制 。 示意圖如下:



??


為了在鴻蒙端更好的接入動態(tài)化 , 有幾點(diǎn)需要特別說明一下:
1.鴻蒙系統(tǒng)的方舟虛擬機(jī)能直接加載js文件嗎?
鴻蒙的開發(fā)語言 ArkTS , 是js語言的超集 , 但為了獲取更高的執(zhí)行效率去除了js語言的動態(tài)性 , 是強(qiáng)類型的編程語言 , ets 文件會被編譯為 abc 文件 (方舟字節(jié)碼文件) , 再由方舟虛擬機(jī)加載運(yùn)行 。 也就是說方舟虛擬機(jī)只能加載和運(yùn)行 abc 文件 , 而無法直接加載 js 文件 。 這為動態(tài)化在鴻蒙端的應(yīng)用帶來了很大的挑戰(zhàn) , 我們一邊嘗試把 V8 移植到鴻蒙(可參考 【史無前例 , 移植V8虛擬機(jī)到純血鴻蒙系統(tǒng)】) , 一邊與華為的工程師溝通建議將 V8 內(nèi)置到鴻蒙系統(tǒng)中 。 最終 , 華為采納了我們的建議 ,提供了一套 JSVM-API , 提供了較為完整的 JS 引擎能力 , 為動態(tài)化在鴻蒙端的適配提供了理論支持 。
2. JS 虛擬機(jī)能高效的執(zhí)行動態(tài)化相關(guān)指令嗎?
為了讓不同業(yè)務(wù)場景下的動態(tài)化產(chǎn)物高效地被 JS 虛擬機(jī)解析執(zhí)行 , 需要擴(kuò)充 JS 虛擬機(jī)的能力 , 提供適合動態(tài)化跨端場景下的功能 , 包括實(shí)例的創(chuàng)建和管理 , 視圖的增、刪、改、查以及便捷的跨語言通訊等功能 。 當(dāng)然 , 為了使各平臺更好的接入和使用動態(tài)化能力 , 動態(tài)化提供了一套統(tǒng)一接口 , 各平臺依靠自身原生能力實(shí)現(xiàn)即可 。
3.鴻蒙端的 App 如何接入動態(tài)化?
為了讓 App 更好的接入動態(tài)化 , 我們提供了鴻蒙端的靜態(tài)庫文件 libRomaSdk.so (后文簡稱 SDK , 由 NDK 通過 CMake 和 Ninja 將 C/C++ 代碼編譯而得來 , 這個過程可參考官網(wǎng) , 這里不展開介紹) , 只要在工程中配置相關(guān)依賴即可使用動態(tài)化能力 。
2.鴻蒙接入動態(tài)化原理介紹動態(tài)化在鴻蒙端的架構(gòu)示意圖如下:



??


App啟動時第一時間就會加載動態(tài)化 SDK , 包括 JS Engine(動態(tài)化自主研發(fā)的為各平臺的 JS 虛擬機(jī)擴(kuò)展的功能) 和 Jue Instance (鴻蒙端對 JS Engine Interface 的實(shí)現(xiàn))兩部分 , 完成動態(tài)化運(yùn)行環(huán)境的初始化 。 具體包括業(yè)務(wù)代碼實(shí)例管理、任務(wù)管理、虛擬Dom樹管理、虛擬Dom樹 Differ、業(yè)務(wù)頁面渲染管理、生命周期管理、事件分發(fā)、業(yè)務(wù)邏輯處理等一系列功能 。
為了適配不同系統(tǒng) , 通過制定統(tǒng)一的對外接口規(guī)范(JS Engine Interface)讓不同平臺按照標(biāo)準(zhǔn)化對接及擴(kuò)展(比如這次華為鴻蒙系統(tǒng)的適配只需實(shí)現(xiàn)統(tǒng)一對外接口即可) , 接口包含了實(shí)例創(chuàng)建、生命周期、元素的增刪改查、JS和原生的雙向通訊、熱重載交互等各種能力 , 各端按需實(shí)現(xiàn) 。
鴻蒙系統(tǒng)上 , Jue Instance 具體實(shí)現(xiàn)接口聲明的方法 , 創(chuàng)建動態(tài)化頁面和處理業(yè)務(wù)交互邏輯 。 提供內(nèi)置基礎(chǔ)標(biāo)簽和模塊 , 同時打通了特殊業(yè)務(wù)場景下的標(biāo)簽和模塊的擴(kuò)展能力 , 業(yè)務(wù)可根據(jù)具體的業(yè)務(wù)場景擴(kuò)充和完善 。
在 JS Engine 環(huán)境中通過對視圖元素的增刪改查操作及其他相關(guān)指令構(gòu)建出了一個組件樹(后文統(tǒng)稱 V-Dom Tree ,作為一個純對象 , 可以清晰的提煉出視圖元素的層級結(jié)構(gòu) , 這個抽象描述是與平臺無關(guān)的 , 因此可在 JS環(huán)境中生成 V-Dom Tree) , 在原生端根據(jù) V-Dom Tree 的結(jié)構(gòu)創(chuàng)建出對應(yīng)的 Render Tree , 再經(jīng)過布局引擎布局后 , 就顯示出了業(yè)務(wù)具體的視圖效果;由于通過統(tǒng)一接口實(shí)現(xiàn)了 JS 和原生的雙向通訊 , 當(dāng)用戶在業(yè)務(wù)視圖上觸發(fā)了交互事件 , 或業(yè)務(wù)方需要改變視圖顯示的時候 , 都可通過統(tǒng)一接口順利的完成事件和數(shù)據(jù)的交互邏輯以及業(yè)務(wù)視圖的渲染 。
三、業(yè)務(wù)示例以在京東金融中加載一個簡單的動態(tài)化頁面為例 , 來分析視圖的加載和更新流程 。 創(chuàng)建一個如下所示的動態(tài)化頁面 , 點(diǎn)擊頁面中“更新節(jié)點(diǎn)數(shù)據(jù)”按鈕 , 修改圖片為一張新圖片 , 并且將一個子視圖的背景色修改為紅色 。 鴻蒙端效果如下所示 。



??


1.業(yè)務(wù)代碼展示首先看動態(tài)化代碼 , 按照前端標(biāo)準(zhǔn)的三段式風(fēng)格 , 上手開發(fā)很容易 。 新建 ADemo.jue 文件( Jue 是我們自定義的開發(fā)語言 , 語法幾乎和 Vue 一致 , 但擴(kuò)展了新增標(biāo)簽的能力) , 代碼如下:
<template style=\"border-width: 2px;\"><div class=\"normalClass\" style=\"margin-top: 100px;\"><image class=\"normalClass\" :src=https://mparticle.uc.cn/"imageUrl\" style=\"height: 200;\"></image><div class=\"normalClass\" :style=\"{'background-color':bgColor\" style=\"height:60px;\"><textstyle=\"border-width: 2px;width:260px;height:40px;align-self: center;text-align: center;background-color: white;\"@click=\"change()\">更新節(jié)點(diǎn)數(shù)據(jù)</text></div></div></template><script>export default {data() {return {bgColor: \"white\"imageUrl: \"https://static.foodtalks.cn/company/images/434/121logo.png\"mounted() {methods: {change() {this.bgColor = \"red\";this.imageUrl = \"https://www.szniego.com/uploads/image/20210202/1612232326.png\";this.updateInstance();</script><style scoped>.normalClass {margin: 10px;justify-content: center;align-items: center;align-self: stretch;border-width: 2px;</style>2.資源加載流程通過平臺線上打包或者使用動態(tài)化提供的腳手架打包命令 , 將 ADemo.jue 打包成 ADemo.zip , 通過資源管理平臺下發(fā)至客戶端設(shè)備 , 經(jīng)過解壓、解密等手段獲取 ADemo.js 等文件 , 原生在合適的時候創(chuàng)建動態(tài)化視圖 , 并通過 JS 虛擬機(jī)加載 ADemo.js 文件 , 從而開始視圖繪制 。
動態(tài)化資源從打包到被 JS 虛擬機(jī)加載的流程圖如下:



??


3.產(chǎn)物代碼展示下面我們來看一下 ADemo.js 文件的具體內(nèi)容 , 截取主要代碼如下:
/******/ (function(modules) { )/************************************************************************//******/ ({/***/ \"./src/jueDemoList/ADemo/ADemo.jue\":/*!*******************************************!*\\!*** ./src/jueDemoList/ADemo/ADemo.jue ***!\\*******************************************//*! exports provided: default *//***/ (function(module __webpack_exports__ __webpack_require__) {\"use strict\";__webpack_require__.r(__webpack_exports__);var template = JRTemplateManager._jr_create_jue_template('ADemo.jue' {\"id\": \"ADemo\"\"version\": \"11\"\"dependencies\": {\"JSEngine\": \"0.9.4\");JRTemplateManager._jr_create_t_node('ADemo.jue' '0' '40e129af-5e6a-70b8-a757-28a22785dc2f' 'document' {\"style\": \"border-width: 2px;\");JRTemplateManager._jr_create_t_node('ADemo.jue' '40e129af-5e6a-70b8-a757-28a22785dc2f' '918d8bb8-9362-bcd6-00ee-35b85c435072' 'div' {\"class\": \"normalClass\"\"style\": \"margin-top: 100px;\");JRTemplateManager._jr_create_t_node('ADemo.jue' '918d8bb8-9362-bcd6-00ee-35b85c435072' '9e848985-59ac-bd8b-e85a-617a6e9a08dd' 'image' {\"class\": \"normalClass\"\":src\": \"imageUrl\"\"style\": \"height: 200;\");JRTemplateManager._jr_create_t_node('ADemo.jue' '918d8bb8-9362-bcd6-00ee-35b85c435072' 'b56d24a9-c08e-6b32-9558-162f2aece68d' 'div' {\"class\": \"normalClass\"\":style\": \"{'background-color':bgColor\"\"style\": \"height:60px;\");JRTemplateManager._jr_create_t_node('ADemo.jue' 'b56d24a9-c08e-6b32-9558-162f2aece68d' '440bad33-f8bb-6baf-fe4f-b9bf768d4cc1' 'text' {\"style\": \"border-width: 2px;width:260px;height:40px;align-self: center;text-align: center;background-color: white;\"\"@click\": \"change()\");JRTemplateManager._jr_add_t_node_value('ADemo.jue' '440bad33-f8bb-6baf-fe4f-b9bf768d4cc1' 'value' '更新節(jié)點(diǎn)數(shù)據(jù)');var __default__ = {data: function data() {return {bgColor: \"white\"imageUrl: \"https://static.foodtalks.cn/company/images/434/121logo.png\";mounted: function mounted() {methods: {change: function change() {this.bgColor = \"red\";this.imageUrl = \"https://www.szniego.com/uploads/image/20210202/1612232326.png\";this.updateInstance();;template.script = __default__;__default__.filename = 'ADemo.jue';__default__.__template__ = function () {return template;;/* harmony default export */ __webpack_exports__[\"default\"
= (__default__);template.globalStyle = {;template.style = {\".normalClass\": {\"margin\": \"10px\"\"justify-content\": \"center\"\"align-items\": \"center\"\"align-self\": \"stretch\"\"border-width\": \"2px\";/***/ )/******/ );//# sourceMappingURL=ADemo.js.map
可以看到頁面數(shù)據(jù)都被封裝到一個自執(zhí)行函數(shù)中 , 在加載資源文件時 , 此函數(shù)就會被調(diào)用 。 函數(shù)內(nèi)頁面數(shù)據(jù)都被保存到__default__變量中 , 視圖以及視圖層級關(guān)系則被解析成一條條節(jié)點(diǎn)相關(guān)的指令 。
四、視圖繪制流程1.繪制原理在鴻蒙端 , 進(jìn)入動態(tài)化頁面之前 , 確保資源文件(ADemo.js)已被加載到內(nèi)存中后 , 由原生端(ArkTS)發(fā)起動態(tài)化實(shí)例的創(chuàng)建 , 調(diào)用動態(tài)化 SDK 提供的創(chuàng)建動態(tài)化實(shí)例(Instance)的方法 , 通過 N-API 調(diào)用宿主環(huán)境(C++)的具體實(shí)現(xiàn) , 創(chuàng)建 C++ 環(huán)境下的 Instance 。 然后通過 JSI (將C++ 中的常用類型與 JavaScript 中的類型一一對應(yīng) , 可互調(diào)方法和操作對象 。 從而消除了數(shù)據(jù)序列化和線程切換調(diào)用的開銷 , 極大提升通訊性能)調(diào)用預(yù)置到 JS 虛擬機(jī)中的 JS Engine 的接口方法 , 創(chuàng)建 JS 環(huán)境下的 Instance 。在 JS 環(huán)境中 , 收集頁面信息 , 完成 V-Dom Tree 的創(chuàng)建 , 再通過 JSI 將頁面數(shù)據(jù)傳遞給宿主環(huán)境(C++) , 根據(jù) V-Dom 的結(jié)構(gòu)創(chuàng)建組件樹(Component Tree)再通過 N-API 將數(shù)據(jù)傳遞給原生 , 調(diào)用 ArkTS 構(gòu)建對應(yīng)的渲染樹(Render Tree) , 完成視圖繪制 。



??


上圖展示了動態(tài)化在鴻蒙端繪制頁面的過程 , 總結(jié)一下就是三種語言環(huán)境 , 三個 Instance , 三個 Thread。 三個 Instance 獨(dú)立且一一對應(yīng) 。 ArkTs 中的 Instance 在 UI 線程中用來完成頁面的繪制 , 數(shù)據(jù)的綁定和事件的觸發(fā)等 。 C++ 中的 Instance 作為數(shù)據(jù)的中轉(zhuǎn)存儲和事件轉(zhuǎn)發(fā) , 在 bg Thread 處理復(fù)雜耗時的邏輯 , 避免阻塞 UI Thread 和 js Thread 。 JS 中的 Instance 負(fù)責(zé)搜集產(chǎn)物信息 , 構(gòu)建 V-Dom Tree , 傳遞數(shù)據(jù)和事件等都在 js Thread 中處理 。 C++ 和 ArkTs 中根據(jù) V-Dom Tree 創(chuàng)建 Component Tree 和 Render Tree 。
通過查看 ADemo.js 資源文件 , 可以得出 V-Dom Tree 的結(jié)構(gòu) , 以及對應(yīng)的 Component Tree 和 Render Tree 。 三者的結(jié)構(gòu)如下圖所示:



??


2.代碼分析下面截取部分核心代碼 , 分析動態(tài)化頁面的創(chuàng)建過程 。 首先在原生端進(jìn)入動態(tài)化頁面 , 創(chuàng)建 RomaInstanceView 對象 , 首先觸發(fā) aboutToAppear 方法 , 準(zhǔn)備創(chuàng)建 ArkTS/C++/JS 三種語言環(huán)境下的 Instance 實(shí)例 。
public aboutToAppear() {// 確保獲取資源js文件并加載到內(nèi)存中romaAssetsManager.ensureAsset(this.jueName! progress).then((version) => {// 完成不同語言環(huán)境下Instance實(shí)例的創(chuàng)建// 1 創(chuàng)建 arkTS 和 cpp 實(shí)例this.romaInstance = this.createInstance(this.rootContent this.pageId);// 2 創(chuàng)建 JS 實(shí)例this.romaInstance!.startInstance(this.initialProps).then((result) => {);).catch((error: Error) => {)1.創(chuàng)建 ArkTS 環(huán)境中的 Instance 實(shí)例通過調(diào)用 createInstance 方法創(chuàng)建 ArkTS 環(huán)境下的 Instance 實(shí)例 , 并傳入 stateListener 參數(shù)監(jiān)聽 Instance 在C++ 語言環(huán)境中的創(chuàng)建過程 , 包括開始創(chuàng)建實(shí)例、創(chuàng)建完成和更新完成的狀態(tài) 。
private createInstance(root: NodeContent pageId: string | null): RomaInstance {this.shouldDestroyRomaInstance = truereturn RomaEnv.createAndRegisterRomaInstance(this.jueName! {root: rootuiContext: this.getUIContext()abilityContext: getContext(this) as common.UIAbilityContextpageId: pageIderrorListener: this.errorListener ? (error) => {this.errorListener?.(error);console.warn(\"CreateInstanceView\" error.message);this.pageStage = PageStage.ERROR: undefinedthis.stateListener);// 類型public stateListener?: RomaStateListener;export type RomaStateListener = (instanceId: string state: \"createFinish\" | \"updateFinish\" | \"createInstance\"romaInstance: RomaInstance) => void;createAndRegisterRomaInstance 方法內(nèi)直接調(diào)用 createInstance 方法完成 ArkTs 環(huán)境中 Instance 的創(chuàng)建 , 并綁定實(shí)例狀態(tài)變化的監(jiān)聽 。
public createInstance(jueName:string param: RomaInstanceParam stateListener?:RomaStateListener): RomaInstance {const id = ++this.nextInstanceId;// 創(chuàng)建 arkTS 側(cè)的實(shí)例const instance = new RomaInstance(jueNameid.toString()\"\"this.napiBridgeparam.uiContextparam.abilityContext)// 給實(shí)例綁定狀態(tài)變化的監(jiān)聽if (stateListener) {instance.addStateListeners(stateListener);instance.setPageId(param.pageId);instance.initialize(param.root);this.instanceMap.set(id.toString() instance);return instance;2.創(chuàng)建 C++ 環(huán)境中的 Instance 實(shí)例在調(diào)用 instance.initialize 中通過 N-API調(diào)用 crateInstance , 準(zhǔn)備在 C++ 環(huán)境中創(chuàng)建 Instance 。 其中第4個參數(shù) (mutations: Mutation[
isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations isFromCore)用來監(jiān)聽 C++ 環(huán)境中 Instance 的創(chuàng)建狀態(tài) 。 當(dāng)后面分析到 C++ 環(huán)境中實(shí)例創(chuàng)建完成時 , 我們在分析 this.descriptorManager.applyMutations(mutations isFromCore) 中具體做了什么 。 這里只需要知道 ArkTs 中的 Instance 在監(jiān)聽 C++ 中的 Instance 的創(chuàng)建過程 。
public initialize(root: NodeContent | null = null) {// 注冊實(shí)例變化監(jiān)聽器this.napiBridge.createInstance( this.jueName this.getId() root(mutations: Mutation[
isFromCore: boolean) => {this.descriptorManager.applyMutations(mutations isFromCore)(tag commandName args) => {// 省略無關(guān)代碼(instanceId: string state: string) => {// 省略無關(guān)代碼)
通過 N-API 在 C++ 環(huán)境中創(chuàng)建對應(yīng)的實(shí)例 , 并在創(chuàng)建 Instance 的回調(diào)中觸發(fā)從 ArkTS 環(huán)境中傳遞過來的狀態(tài)監(jiān)聽 ,auto listener = arkJs.getReferenceValue(listener_ref); arkJs.call<1>(listener args); 從而將 C++ 中 Instance 創(chuàng)建過程傳遞到 ArkTs 的 Instance 中 。
static napi_value createInstance(napi_env env napi_callback_info info) {ArkTS arkJs(env);auto args = arkJs.getCallbackArgs(info 6);auto jueName = arkJs.getString(args[0
);InstanceId instanceId = arkJs.getString(args[1
);ArkUI_NodeContentHandle nodeContentHandle_;if(!arkJs.isUndefined(args[2
)){OH_ArkUI_GetNodeContentFromNapiValue(env args[2
&nodeContentHandle_);auto listener_ref = arkJs.createReference(args[3
);auto componentMethod_ref = arkJs.createReference(args[4
);auto stateListenerMethod_ref = arkJs.createReference(args[5
);auto &engine = RomaEnv::getInstance();engine.createInstance(jueName instanceId nodeContentHandle_[env listener_ref
(MutationsToNapiConverter mutationsToNapiConverter auto const &mutations) {// C++環(huán)境中 Instance 狀態(tài)變化時觸發(fā)if (mutations.size() <= 0) {return;ArkTS arkJs(env);auto napiMutations = mutationsToNapiConverter.convert(env mutations);std::array<napi_value 1> args = {napiMutations;// 獲取從 ArkTS 環(huán)境中傳遞過來的監(jiān)聽對象并觸發(fā)auto listener = arkJs.getReferenceValue(listener_ref);arkJs.call<1>(listener args);return arkJs.getUndefined();
在 C++ 環(huán)境中創(chuàng)建 Instance 的具體實(shí)現(xiàn)如下 , 記錄下從 ArkTs 中獲取的狀態(tài)監(jiān)聽參數(shù) mutationsListener 以便在合適的時機(jī)觸發(fā) 。
void RomaEnv::createInstance(std::string jueName InstanceId instanceId ArkUI_NodeContentHandle nodeContentHandle_ MutationsListener mutationsListenerComponentMethodListener componentMethodListener StateListener stateListener) {RomaEnv::getInstance().getBackgroundExecutor()([=
() {auto nonConstRef = RomaInstanceManager::getInstance().get(instanceId);// 創(chuàng)建實(shí)例 , 頁面 | 模板RomaInstance::Shared instance = std::make_shared<RomaInstance>(jueName instanceId);instance->rootInstance_ = instance;instance->attachNativeXComponent(nodeContentHandle_);// 注冊監(jiān)聽器instance->registerInstanceChangeListener(mutationsListener);……);
3.創(chuàng)建 JS 環(huán)境中的 Instance 實(shí)例下面分析 JS 端 Instance 的創(chuàng)建過程 , 接上面 this.romaInstance!.startInstance(this.initialProps).then((result) => {); 往下看
public async startInstance(initialProps: TObject): Promise<void> {return this.napiBridge.startInstance(this.getId()initialProps);在 ArkTs 端通過 N-API 調(diào)用 SDK 中的 startInstance 進(jìn)入 C++環(huán)境
static napi_value startInstance(napi_env env napi_callback_info info) {ArkTS arkJs(env);arkJs.methodName = \"startInstance\";auto args = arkJs.getCallbackArgs(info 3);InstanceId instanceId = arkJs.getString(args[0
);auto onFinishRef = https://mparticle.uc.cn/api/arkJs.createReference(args[2
);auto &engine = RomaEnv::getInstance();engine.startInstance(instanceId arkJs.getDynamic(args[1
) [env onFinishRef
() {ArkTS arkJs(env);auto listener = arkJs.getReferenceValue(onFinishRef);arkJs.call<0>(listener {);arkJs.deleteReference(onFinishRef););return arkJs.getUndefined(); // startInstance 的具體實(shí)現(xiàn)void RomaEnv::startInstance(InstanceId instanceId folly::dynamic &&initialPropsstd::function<void()> &&onFinish) {try {RomaEnv::getInstance().getBackgroundExecutor()([=
() {auto nonConstRef = RomaInstanceManager::getInstance().get(instanceId);if (nonConstRef) {// 準(zhǔn)備進(jìn)入 js 環(huán)境創(chuàng)建 InstancenonConstRef->start(instanceId initialProps);this->taskExecutor_->runTask(TaskThread::MAIN [onFinish
() {onFinish();););catch (const std::exception &e) {throw e.what();;
在 start 方法中開始創(chuàng)建 js 環(huán)境下的 Instance , 通過 JSI 獲取 JS 虛擬機(jī)對象 runtime, 調(diào)用在 App 啟動時就注入到 JS 虛擬機(jī)中的 JRPageManager 對象的 createInstance 方法
voidRomaInstance::start(SurfaceId surfaceId folly::dynamic const &initialProps){// 進(jìn)入js線程執(zhí)行實(shí)例創(chuàng)建RomaEnv::getInstance().getRuntimeExecutor()([jueName_ = jueName_initialProps surfaceId
(jsi::Runtime &runtime) {// 判斷 JS 的全局變量中是否有 JRPageManagerif (runtime.global().hasProperty(runtime \"JRPageManager\")) {jsi::Object JRPageManager = runtime.global().getPropertyAsObject(runtime \"JRPageManager\");if (JRPageManager.hasProperty(runtime \"createInstance\")) {jsi::Function method = JRPageManager.getPropertyAsFunction(runtime \"createInstance\");method.callWithThis(runtime JRPageManager{jsi::valueFromDynamic(runtime jueName_+\".jue\")jsi::valueFromDynamic(runtime surfaceId)jsi::valueFromDynamic(runtime initialProps)););
進(jìn)入 JS 環(huán)境后調(diào)用 createInstance 方法 , 開始創(chuàng)建 JS 環(huán)境下的 Instance
export function createInstance(bundleNameinstanceIDoptions){// 判斷是否傳入實(shí)例bundleName以及對應(yīng)bundle是否已經(jīng)加載if (!bundleName || !has_load_bundle(bundleName)) {callLoadBundleJsFileFail(instanceID)return;JRTransUICore._jr_ydby_new_template_instance(bundleNameinstanceIDoptions);在 JS環(huán)境中完成 Instance 創(chuàng)建后 , 調(diào)用 _jr_ydby_new_node_instance 開始構(gòu)建 V-Dom Tree
function _jr_ydby_new_template_instance(template_id ctx_id template_data is_batchCreate) {// 1.根據(jù)模板創(chuàng)建JUE實(shí)例var ctx = new JueInstance(ctx_id template template_data);ctx.initRootCtxAndStaticCss();……// 創(chuàng)建v-domvar v_dom = new _jr_ydby_v_dom(template_id);ctx.v_dom = v_dom;// 2.構(gòu)建v-dom 對應(yīng) root-nodevar root_node = _jr_ydby_new_node_instance(ctx.c_id v_dom template.root_node null { is_batchCreate);// 構(gòu)建結(jié)束_jr_ydby_create_finshed(ctx.c_id);return ctx.c_id;4.在 JS 環(huán)境中構(gòu)建 V-Dom Tree通過 _jr_ydby_new_node_instance 方法 , 遍歷視圖中所有節(jié)點(diǎn)及子節(jié)點(diǎn) , 完成整個 V-Dom Tree 的創(chuàng)建
export function _jr_ydby_new_node_instance(ctx_id v_dom current_t_node parent_node v_f_ctx is_batchCreate itemIndex) {var cur_env = _jr_ydby_node_parse_jscontext(ctx_id v_f_ctx);let ctx = __jr_template__ctx[ctx_id
;var current_node = null;if (current_t_node.type === 'document') {// 創(chuàng)建根節(jié)點(diǎn)current_node = new JUE_NODE(v_f_ctx ctx_id \"\" current_t_node);current_node.setAttr(cur_env ctx)_jr_ydby_create_body(current_node);else{// 創(chuàng)建其他子節(jié)點(diǎn)current_node = new JUE_NODE(v_f_ctx ctx_id parent_node.id current_t_node);current_node.setAttr(cur_env ctx);current_node.setStyle(cur_env parent_node.style);current_node.setValue(cur_env v_f_ctx);current_node.setEvent(cur_env);// 組裝數(shù)據(jù) , 生成 _jr_ydby_v_node對象parent_node.appendChild(current_node v_dom);// 添加節(jié)點(diǎn)_jr_ydby_add_element(current_node current_node.node_index);// 循環(huán)創(chuàng)建當(dāng)前節(jié)點(diǎn)下的子節(jié)點(diǎn)if (current_t_node.sub_nodes) {let v_if_else_parse_result = [
;for (let i = 0; i < current_t_node.sub_nodes.length; i++) {v_f_ctx = Object.assign({ v_f_ctx);let sub_t_node = current_t_node.sub_nodes[i
;let att = sub_t_node.attr;let v_for_value = https://mparticle.uc.cn/api/att['v-for'
;……// 遞歸創(chuàng)建子節(jié)點(diǎn)_jr_ydby_new_node_instance(ctx_id v_dom sub_t_node current_node v_f_ctx is_batchCreate);return current_node;
5.在 C++ 環(huán)境中構(gòu)建 Component Tree在構(gòu)建 V-Dom Tree 的過程中 , 每創(chuàng)建一個節(jié)點(diǎn) , 都會調(diào)用 _jr_ydby_add_element 方法 , 向宿主C++環(huán)境發(fā)起創(chuàng)建節(jié)點(diǎn)的指令:
_jr_ydby_add_element(current_node current_node.node_index);// 搜集節(jié)點(diǎn)信息var nodePorperty = {template_id: node.template_idctx_d: node.ctx_idtag: node.tagid: node.idis_root: node.is_roottype: node.typeparent_node: node.parent_nodestyle: node.stylecache: node.cacheattr: _jr_ydby_tools_deep_copy(node.attr)value: node.valueevent: node.eventindex: node.index//僅cell-slot節(jié)點(diǎn)使用isComponentNode: node.isComponentNodecomponentInstanceId: node.componentInstanceIdcomponentBundleName: node.componentBundleName;// 調(diào)用添加方法callAddElement(node.ctx_id node.parent_node nodePorperty index);在 App 啟動時 , 已向 JS 虛擬機(jī)內(nèi)植入 callAddElement 方法 , 這里通過 JSI通道將指令從 JS 打通到C++環(huán)境
runtime_->global().setProperty( *runtime_ \"callAddElement\"Function::createFromHostFunction( *runtime_PropNameID::forAscii(*runtime_ \"callAddElement\") 4[
(jsi::Runtime &runtimejsi::Value const & /*thisValue*/jsi::Value const *argumentssize_t /*count*/) noexcept -> jsi::Value {UIManager::callAddElement(surfaceIdFromValue(runtime arguments[0
)stringFromValue(runtime arguments[1
)commandArgsFromValue(runtime arguments[2
)arguments[3
.getNumber());return jsi::Value::undefined();));
在C++環(huán)境中接收到添加指令后 , 這里開始構(gòu)建 Component Tree , 保存各節(jié)點(diǎn)數(shù)據(jù)信息 , 并不會同步在界面上添加視圖(因?yàn)槊看问褂?N-API在 C++ 和 ArkTS 中通訊 , 都會因跨語言和類型轉(zhuǎn)換帶來一定的性能損耗) , 待接收到 V-Dom Tree 構(gòu)建完成的消息后 , 對應(yīng)的 Component Tree 的結(jié)構(gòu)也構(gòu)建完成了 , 這時再一次性完成視圖的繪制 。
void UIManager::callAddElement(SurfaceId surfaceId std::string const &parent_id folly::dynamic props size_t index) {ComponentName name = props[\"type\"
.asString();RomaEnv::getInstance().getBackgroundExecutor()([=
() {RomaNode::Shared node = nullptr;auto nonConstRef= RomaInstanceManager::getInstance().get(surfaceId);auto parent = nonConstRef->getNode(parent_id);if(parent == nullptr){return;// 強(qiáng)引用保存節(jié)點(diǎn)到父節(jié)點(diǎn)的children_數(shù)組中folly::dynamic style = props[\"style\"
;node = RomaNodeFactory::createSharedNode(surfaceId tag name parent_id index isComponentNodecomponentInstanceId props[\"attr\"
style props[\"event\"
props[\"value\"
);size_t childIndex = index;parent->appendChild(node childIndex);auto shadowView = std::make_shared<ShadowView>(*node);// 組件節(jié)點(diǎn) , 只插入 , 不創(chuàng)建 , 等組件實(shí)例創(chuàng)建時 , 再創(chuàng)建if (!isComponentNode) {// 增加普通節(jié)點(diǎn)的創(chuàng)建指令nonConstRef->rootInstance_.lock()->lastMutations_.push_back(ShadowViewMutation::CreateMutation(shadowView->getSharedShadowView()));if ((parent->getComponentName() == \"document\") && nonConstRef->isComponent) {// 如果是子組件中的節(jié)點(diǎn) , 并且是document的直接子節(jié)點(diǎn)auto parentNonConstRef = RomaInstanceManager::getInstance().get(nonConstRef->parentInstanceId_);if (parentNonConstRef) {auto componentNode = parentNonConstRef->getNode(nonConstRef->componentNodeId_);if (componentNode) {if (RomaEnv::getInstance().isUseYoga) {// 父子組件銜接yoga樹componentNode->appendChild(node index);// 增加組件節(jié)點(diǎn)的插入指令auto parentShadowView = componentNode->getShadowView();auto documentShadowView = parent->getShadowView();……for (auto const &pair : documentShadowView->style_.items()) {parentShadowView->style_[pair.first
= pair.second;nonConstRef->rootInstance_.lock()->lastMutations_.push_back(ShadowViewMutation::InsertMutation(parentShadowView shadowView->getSharedShadowView() index));else {// 增加普通節(jié)點(diǎn)的插入指令auto parentShadowView = parent->getShadowView();nonConstRef->rootInstance_.lock()->lastMutations_.push_back(ShadowViewMutation::InsertMutation(parentShadowView shadowView->getSharedShadowView() childIndex));// 保存到節(jié)點(diǎn)node->setShadowView(shadowView->getSharedShadowView());// 弱引用保存節(jié)點(diǎn)到實(shí)例的map中nonConstRef->addNode(tag node););
6. 在 JS 環(huán)境中發(fā)送節(jié)點(diǎn)創(chuàng)建完成的消息V-Dom Tree 構(gòu)建完成后 , 會調(diào)用 _jr_ydby_create_finshed
export function _jr_ydby_create_finshed(ctx_id) {let instance = getInstanceById(ctx_id);callCreateFinish(ctx_id{template_id:instance.template_idversion:instance.currentVersion);同樣在動態(tài)化 SDK 初始化階段 , 已向 JS虛擬機(jī)中植入callCreateFinish方法 , 當(dāng) JS 端調(diào)用callCreateFinish時 , 通過 JSI 通道將數(shù)據(jù)傳遞到 C++環(huán)境中 。
runtime_->global().setProperty(*runtime_ \"callCreateFinish\"Function::createFromHostFunction(*runtime_PropNameID::forAscii(*runtime_ \"callCreateFinish\") 1[
(jsi::Runtime &runtime jsi::Value const &jsi::Value const *arguments size_t /*count*/) noexcept -> jsi::Value {auto surfaceId = surfaceIdFromValue(runtime arguments[0
);UIManager::callCreateFinish(surfaceId);return jsi::Value::undefined();));
在C++環(huán)境中收到創(chuàng)建完成的消息后 , 執(zhí)行 yoga 布局 , 獲取視圖尺寸并調(diào)用在 Instance 創(chuàng)建過程中預(yù)置的狀態(tài)監(jiān)聽 mutationsListener 方法 。
void UIManager::callCreateFinish(SurfaceId surfaceId) {RomaEnv::getInstance().getBackgroundExecutor()([=
() {auto nonConstRef = RomaInstanceManager::getInstance().get(surfaceId);if (nonConstRef && !nonConstRef->isComponent) {// 執(zhí)行yoga布局// Layout nodes.std::vector<YogaLayoutableShadowNode const *> affectedLayoutableNodes{;// affectedLayoutableNodes.reserve(1024);LayoutContext layoutContext = LayoutContext();……if(starts_with(nonConstRef->rootInstance_.lock()->jueName_ \"template\")){layoutContext.layoutType = TEMPLATE;if (nonConstRef->rootInstance_.lock()) {nonConstRef->rootInstance_.lock()->rootNode_->layoutIfNeeded(layoutContext);if (nonConstRef->rootInstance_.lock()) {ShadowViewMutationList mutableList = nonConstRef->rootInstance_.lock()->lastMutations_;nonConstRef->rootInstance_.lock()->lastMutations_.clear();RomaEnv::getInstance().taskExecutor_->runTask(TaskThread::MAIN [nonConstRef mutableList surfaceId
{// 使用 ArkUI 渲染, 并觸發(fā) mutationsListener 監(jiān)聽if (nonConstRef->rootInstance_.lock()) {auto a = nonConstRef->rootInstance_.lock()->m_mutationsToNapiConverter;nonConstRef->rootInstance_.lock()->mutationsListener(a mutableListtrue);nonConstRef->createFinish(););else if (nonConstRef) {RomaEnv::getInstance().taskExecutor_->runTask(TaskThread::MAIN [nonConstRef surfaceId
{ nonConstRef->createFinish(); ););
下面我們具體分析一下在觸發(fā) mutationsListener 后 , 都發(fā)生了什么?通過 C++ 的轉(zhuǎn)發(fā) , 最終在 ArkTS 環(huán)境中觸發(fā)如下回調(diào)(這是在 ArkTS創(chuàng)建 Instance 時就通過 N-API傳入到 C++ 環(huán)境的參數(shù)之一 , 上面有介紹)
(mutations: Mutation[
isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations isFromCore)
7.在 ArkTS 環(huán)境中構(gòu)建 Render Tree實(shí)例創(chuàng)建完成后觸發(fā) applyMutations 方法 , 將各節(jié)點(diǎn)數(shù)據(jù)都保存到 descriptor 中 。
public applyMutations(mutations: Mutation[
isFromCore: boolean) {// 去重const tags = mutations.flatMap(mutation => this.applyMutation(mutation isFromCore));const tags = new Set(tags);// 遍歷各節(jié)點(diǎn)tags.forEach(tag => {// 取實(shí)例id , 和tagconst strArr: string[
= tag.split(\"##\");const instanceId = strArr[0
;//實(shí)例idconst nodeId = strArr[1
;//節(jié)點(diǎn)idif(instanceId === this.romaInstance.getId()){// 更新節(jié)點(diǎn)let updatedDescriptor = this.getDescriptor(nodeId);if(!updatedDescriptor) return;// 在當(dāng)前實(shí)例中更新tag組件的UI描述信息this.descriptorListenersSetByTag.get(nodeId)?.forEach(cb => {onDescriptorChange(cb updatedDescriptor););else {// 創(chuàng)建節(jié)點(diǎn)const instance: RomaInstance = RomaEnv.getRomaInstanceManager()?.getInstance(instanceId) as RomaInstance;let updatedDescriptor= instance?.getDescriptor(nodeId);if(!updatedDescriptor) return;instance.refreshComponentUI(nodeId updatedDescriptor););
在 refreshComponentUI 方法中會觸發(fā)當(dāng)前節(jié)點(diǎn)對應(yīng)標(biāo)簽的數(shù)據(jù)變化監(jiān)聽
public refreshComponentUI(tag: Tag d: Descriptor) {this.getDescriptorListenersSet(tag)?.forEach(cb => {onDescriptorChange(cbd););假如標(biāo)簽是 image, 則觸發(fā) image 標(biāo)簽的在 aboutToAppear 中的關(guān)于標(biāo)簽數(shù)據(jù) descriptor 的監(jiān)聽 , 從 newDescriptor 中獲取標(biāo)簽上所有的數(shù)據(jù) , 包括尺寸數(shù)據(jù) 。
aboutToAppear() {if (!this.componentCtx) {return;this.componentCtx?.aboutToAppear((newDescriptor) => {this.descriptor = newDescriptor;// 鏈接自定義alt圖方法this.customAltImage = RomaConfig.instance().getImageAltMethod();// 觸發(fā)更新this.onLoadStart();this.updateImageSource();// 處理圖片 object-position 模式相關(guān)邏輯this.initObjectPositionHandle()// 解析占位圖this.altSource = this.getImageAlt();this.hasPlaceHoldImage = this.altSource ? true : false;// 解析背景色let bgColorStr = RomaStyleParser.getStyleToString('background-color'this.descriptor);……// 設(shè)置tint-colorthis.getTintColor();// 是否開啟抗鋸齒this.interpolation = this.colorFilter ? ImageInterpolation.High : ImageInterpolation.Low;(methodName args:TAny[
) => {// 注冊標(biāo)簽方法if (methodName === 'loadRef') {this.loadRef(args[0
););
8.根據(jù) Render Tree 繪制視圖到此我們也僅僅是完成了進(jìn)入動態(tài)化頁面 RomaInstanceView 視圖中的 abountToAppear 方法中的數(shù)據(jù)準(zhǔn)備工作 。 完成了三個 Instance 的創(chuàng)建 , 完成了三棵樹的創(chuàng)建以及把所有組件和標(biāo)簽相關(guān)數(shù)據(jù)都保存到對應(yīng)的 descriptor 中 , 接下來就是在 build 方法中真正繪制視圖了 。
public build() {// 根據(jù)表述信息 , 構(gòu)建RomaComponentFactory.builder(new RomaComponentParam(this.romaInstance this.descriptor.tag));RomaComponentFactory是所有標(biāo)簽組件的工廠方法 , 定義如下:
export const RomaComponentFactory: WrappedBuilder<[RomaComponentParam
> = wrapBuilder(RomaComponentFactoryBuilder);
在調(diào)用 RomaComponentFactory.builder 時 , 觸發(fā) RomaComponentFactoryBuilder 方法 , 如下只列出示例中用到的標(biāo)簽的實(shí)現(xiàn) , 其他的省略了 。
param.descriptor 中保存了已經(jīng)構(gòu)建好的 Render Tree , 包括各節(jié)點(diǎn)對應(yīng)的標(biāo)簽類型和所有的節(jié)點(diǎn)數(shù)據(jù) 。
@Builderfunction RomaComponentFactoryBuilder(param: RomaComponentParam) {if (param.type == \"document\") {RomaDocument({componentCtx: param.componentCtx)else if (param.type == \"div\") {RomaDiv({componentCtx: param.componentCtx)else if (param.type === \"text\") {RomaText({componentCtx: param.componentCtx)else if (param.type === \"image\" || param.type === \"img\") {RomaImageView({componentCtx: param.componentCtx)else {RomaCustomComponentFactory.customComponentBuilder.builder(param.componentCtx);以圖片標(biāo)簽為例 , 會創(chuàng)建 RomaImageView 對象 , 調(diào)用其 build 方法 , 最終將 image 視圖渲染到頁面上 。
build() {if(this.componentCtx && this.descriptor) {Image(this.imgSource).attributeModifier(this.componentCtx?.build(this.descriptor)).gestureModifier(this.componentCtx?.build(this.descriptor)).alt(this.getImageAlt()).objectFit(this.getResizeMode(RomaStyleParser.getStyleToString('object-fit' this.descriptor))).renderMode(this.getRenderMode()).colorFilter(this.colorFilter).interpolation(this.interpolation).backgroundColor(this.showColor).blur(this.getBlurNumber()).onComplete(event => this.onLoad(event as ImageOnCompleteEvent)).onError(event => this.dispatchOnError(event as ImageOnErrorEvent)).position(this.imgPosition).clipShape(this.imgClipShape)至此!動態(tài)化終不辱使命 , 一路逢山開路 , 遇水架橋 , 跨越層層關(guān)隘 , 破天命所縛 , 所言終達(dá)天聽 , 輔社稷 , 開盛世太平!就是說動態(tài)化完成了從資源被加載-到數(shù)據(jù)被層層加工流轉(zhuǎn)-到視圖被創(chuàng)建-再到渲染到頁面上的全過程 。
五、視圖更新流程當(dāng)點(diǎn)擊“更新節(jié)點(diǎn)數(shù)據(jù)”按鈕后 , 圖片資源被修改 , div視圖的背景色也被修改 。 相比創(chuàng)建的過程 , 這個頁面中節(jié)點(diǎn)數(shù)據(jù)的更新要簡單一些 , 因?yàn)楦鳝h(huán)境下的 Instance 都已經(jīng)創(chuàng)建好了 , 各環(huán)境中的通道也打通了 , 只是各節(jié)點(diǎn)Differ(數(shù)據(jù)對比)的過程 , 并將 Differ 結(jié)果通過各環(huán)境中的通道傳遞給相關(guān)視圖節(jié)點(diǎn)更新即可 。 這里不詳細(xì)介紹具體的過程了 , 因?yàn)閿?shù)據(jù)在三種語言環(huán)境中傳遞的邏輯是一致的 , 這里只介紹一下 Differ 的邏輯 。
1.Differ 原理介紹當(dāng)業(yè)務(wù)需要更新視圖的時候 , 會根據(jù)新的視圖數(shù)據(jù)重新生成一棵新的 V-Dom Tree , 和頁面舊的 V-Dom Tree 進(jìn)行對比 , 最終得到需要更新的節(jié)點(diǎn)數(shù)組 , 將這個數(shù)組同步到 ArkTS 的 Render Tree 的對應(yīng)的節(jié)點(diǎn) , 觸發(fā)相應(yīng)節(jié)點(diǎn)更新 。



??


六、規(guī)劃總結(jié)目前已使用三個線程確保數(shù)據(jù)在不同環(huán)境中的高效處理 , 為了避免阻塞 JS 線程和 UI 線程 , 已將復(fù)雜耗時的功能放到 bg 線程中處理 , 盡可能的提升了頁面繪制效率 。 但使用 ArkUI 封裝好的視圖組件繪制的視圖層級相比 Android 和 iOS端多出一倍 , 且使用N-API通訊會帶來一定的性能開銷 , 因此先天性的多做了很多的工作 。 為了進(jìn)一步提升視圖渲染和數(shù)據(jù)通訊的效率 , 計(jì)劃接下來將 C-API (鴻蒙提供的一組繪制視圖的 C 接口)接入到動態(tài)化鴻蒙 SDK 中 , 在 C++ 環(huán)境中就完成視圖的繪制 , 以更直接和高效的方式繪制視圖 , 視圖的層級將減少一半 , 同時省去了跨語言通訊的相關(guān)成本 。 經(jīng)測試 , 視圖繪制和渲染效率將進(jìn)一步提升 , 用戶將獲得更好的使用體驗(yàn) 。
【鴻蒙跨端實(shí)踐-揭秘視圖渲染流程】動態(tài)化是一個涉及 Android、iOS、Harmony、Web、Java、C/C++、Vue、JavaScript、Node、Webpack、CLang、Ninja 等眾多領(lǐng)域的綜合解決方案 。 我們有各個領(lǐng)域優(yōu)秀的小伙伴共同前行 , 如果你想深入了解某個領(lǐng)域的具體實(shí)現(xiàn) , 可在評論區(qū)留言隨時交流~!

    推薦閱讀