首页 » SEO优化 » phpimageupdate技巧_鸿蒙跨端实践揭秘视图衬着流程

phpimageupdate技巧_鸿蒙跨端实践揭秘视图衬着流程

访客 2024-11-20 0

扫一扫用手机浏览

文章目录 [+]



为了在鸿蒙端更好的接入动态化,有几点须要特殊解释一下:

phpimageupdate技巧_鸿蒙跨端实践揭秘视图衬着流程

1.鸿蒙系统的方舟虚拟性能直接加载js文件吗?

phpimageupdate技巧_鸿蒙跨端实践揭秘视图衬着流程
(图片来自网络侵删)

鸿蒙的开拓措辞 ArkTS,是js措辞的超集,但为了获取更高的实行效率去除了js措辞的动态性,是强类型的编程措辞,ets 文件会被编译为 abc 文件 (方舟字节码文件),再由方舟虚拟机加载运行。
也便是说方舟虚拟机只能加载和运行 abc 文件,而无法直接加载 js 文件。
这为动态化在鸿蒙真个运用带来了很大的寻衅,我们一边考试测验把 V8 移植到鸿蒙(可参考 【亘古未有,移植V8虚拟机到纯血鸿蒙系统】),一边与华为的工程师沟通建议将 V8 内置到鸿蒙系统中。
终极,华为采纳了我们的建议, 供应了一套 JSVM-API,供应了较为完全的 JS 引擎能力,为动态化在鸿蒙真个适配供应了理论支持。

2. JS 虚拟性能高效的实行动态化干系指令吗?

为了让不同业务场景下的动态化产物高效地被 JS 虚拟机解析实行,须要扩充 JS 虚拟机的能力,供应适宜动态化跨端场景下的功能,包括实例的创建和管理,视图的增、删、改、查以及便捷的跨措辞通讯等功能。
当然,为了使各平台更好的接入和利用动态化能力,动态化供应了一套统一接口,各平台依赖自身原生能力实现即可。

3.鸿蒙真个 App 如何接入动态化?

为了让 App 更好的接入动态化,我们供应了鸿蒙真个静态库文件 libRomaSdk.so (后文简称 SDK,由 NDK 通过 CMake 和 Ninja 将 C/C++ 代码编译而得来,这个过程可参考官网,这里不展开先容),只要在工程中配置干系依赖即可利用动态化能力。

2.鸿蒙接入动态化事理先容

动态化在鸿蒙真个架构示意图如下:



App启动时第一韶光就会加载动态化 SDK,包括 JS Engine(动态化自主研发的为各平台的 JS 虚拟机扩展的功能) 和 Jue Instance (鸿蒙端对 JS Engine Interface 的实现)两部分,完成动态化运行环境的初始化。
详细包括业务代码实例管理、任务管理、虚拟Dom树管理、虚拟Dom树 Differ、业务页面渲染管理、生命周期管理、事宜分发、业务逻辑处理等一系列功能。

为了适配不同系统,通过制订统一的对外接口规范(JS Engine Interface)让不同平台按照标准化对接及扩展(比如这次华为鸿蒙系统的适配只需实现统一对外接口即可),接口包含了实例创建、生命周期、元素的增编削查、JS和原生的双向通讯、热重载交互等各种能力,各端按需实现。

鸿蒙系统上,Jue Instance 详细实现接口声明的方法,创建动态化页面和处理业务交互逻辑。
供应内置根本标签和模块,同时打通了分外业务场景下的标签和模块的扩展能力,业务可根据详细的业务场景扩充和完善。

在 JS Engine 环境中通过对视图元素的增编削查操作及其他干系指令构建出了一个组件树(后文统称 V-Dom Tree, 作为一个纯工具,可以清晰的提炼出视图元素的层级构造,这个抽象描述是与平台无关的,因此可在 JS环境中天生 V-Dom Tree),在原生端根据 V-Dom Tree 的构造创建出对应的 Render Tree,再经由布局引擎布局后,就显示出了业务详细的视图效果;由于通过统一接口实现了 JS 和原生的双向通讯,当用户在业务视图上触发了交互事宜,或业务方须要改变视图显示的时候,都可通过统一接口顺利的完成事宜和数据的交互逻辑以及业务视图的渲染。

三、业务示例

以在京东金融中加载一个大略的动态化页面为例,来剖析视图的加载和更新流程。
创建一个如下所示的动态化页面,点击页面中“更新节点数据”按钮,修正图片为一张新图片,并且将一个子视图的背景色修正为赤色。
鸿蒙端效果如下所示。



1.业务代码展示

首先看动态化代码,按照前端标准的三段式风格,上手开拓很随意马虎。
新建 ADemo.jue 文件( Jue 是我们自定义的开拓措辞,语法险些和 Vue 同等,但扩展了新增标签的能力),代码如下:

<template style="border-width: 2px;"> <div class="normalClass" style="margin-top: 100px;"> <image class="normalClass" :src="imageUrl" style="height: 200;"> </image> <div class="normalClass" :style="{'background-color':bgColor}" style="height:60px;"> <text style="border-width: 2px;width:260px;height:40px;align-self: center;text-align: center;background-color: white;" @click="change()"> 更新节点数据 </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.资源加载流程

通过平台线上打包或者利用动态化供应的脚手架打包命令,将 ADemo.jue 打包成 ADemo.zip,通过资源管理平台下发至客户端设备,经由解压、解密等手段获取 ADemo.js 等文件,原生在得当的时候创建动态化视图,并通过 JS 虚拟机加载 ADemo.js 文件,从而开始视图绘制。

动态化资源从打包到被 JS 虚拟机加载的流程图如下:



3.产物代码展示

下面我们来看一下 ADemo.js 文件的详细内容,截取紧张代码如下:

// (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', '更新节点数据');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

可以看到页面数据都被封装到一个自实行函数中,在加载资源文件时,此函数就会被调用。
函数内页面数据都被保存到__default__变量中,视图以及视图层级关系则被解析成一条条节点干系的指令。

四、视图绘制流程1.绘制事理

在鸿蒙端,进入动态化页面之前,确保资源文件(ADemo.js)已被加载到内存中后,由原生端(ArkTS)发起动态化实例的创建,调用动态化 SDK 供应的创建动态化实例(Instance)的方法,通过 N-API 调用宿主环境(C++)的详细实现,创建 C++ 环境下的 Instance。
然后通过 JSI (将C++ 中的常用类型与 JavaScript 中的类型逐一对应,可互调方法和操为难刁难象。
从而肃清了数据序列化和线程切换调用的开销,极大提升通讯性能)调用预置到 JS 虚拟机中的 JS Engine 的接口方法,创建 JS 环境下的 Instance。
在 JS 环境中,网络页面信息,完成 V-Dom Tree 的创建,再通过 JSI 将页面数据通报给宿主环境(C++),根据 V-Dom 的构造创建组件树(Component Tree),再通过 N-API 将数据通报给原生,调用 ArkTS 构建对应的渲染树(Render Tree),完成视图绘制。



上图展示了动态化在鸿蒙端绘制页面的过程,总结一下便是三种措辞环境,三个 Instance,三个 Thread 。
三个 Instance 独立且逐一对应。
ArkTs 中的 Instance 在 UI 线程中用来完成页面的绘制,数据的绑定和事宜的触发等。
C++ 中的 Instance 作为数据的中转存储和事宜转发,在 bg Thread 处理繁芜耗时的逻辑,避免壅塞 UI Thread 和 js Thread。
JS 中的 Instance 卖力搜集产物信息,构建 V-Dom Tree,通报数据和事宜等都在 js Thread 中处理。
C++ 和 ArkTs 中根据 V-Dom Tree 创建 Component Tree 和 Render Tree。

通过查看 ADemo.js 资源文件,可以得出 V-Dom Tree 的构造,以及对应的 Component Tree 和 Render Tree。
三者的构造如下图所示:



2.代码剖析

下面截取部分核心代码,剖析动态化页面的创建过程。
首先在原生端进入动态化页面,创建 RomaInstanceView 工具,首先触发 aboutToAppear 方法,准备创建 ArkTS/C++/JS 三种措辞环境下的 Instance 实例。

public aboutToAppear() { // 确保获取资源js文件并加载到内存中 romaAssetsManager.ensureAsset(this.jueName!, progress).then((version) => { // 完身分歧措辞环境下Instance实例的创建 // 1 创建 arkTS 和 cpp 实例 this.romaInstance = this.createInstance(this.rootContent, this.pageId); // 2 创建 JS 实例 this.romaInstance!.startInstance(this.initialProps).then((result) => {}); }).catch((error: Error) => { }) }1.创建 ArkTS 环境中的 Instance 实例

通过调用 createInstance 方法创建 ArkTS 环境下的 Instance 实例,并传入 stateListener 参数监听 Instance 在C++ 措辞环境中的创建过程,包括开始创建实例、创建完成和更新完成的状态。

private createInstance(root: NodeContent, pageId: string | null): RomaInstance { this.shouldDestroyRomaInstance = true return RomaEnv.createAndRegisterRomaInstance(this.jueName!, { root: root, uiContext: this.getUIContext(), abilityContext: getContext(this) as common.UIAbilityContext, pageId: pageId, errorListener: this.errorListener ? (error) => { this.errorListener?.(error); console.warn("CreateInstanceView", error.message); this.pageStage = PageStage.ERROR } : undefined }, this.stateListener); } // 类型 public stateListener?: RomaStateListener; export type RomaStateListener = (instanceId: string, state: "createFinish" | "updateFinish" | "createInstance", romaInstance: RomaInstance) => void;

createAndRegisterRomaInstance 方法内直接调用 createInstance 方法完成 ArkTs 环境中 Instance 的创建,并绑定实例状态变革的监听。

public createInstance(jueName:string, param: RomaInstanceParam, stateListener?:RomaStateListener): RomaInstance { const id = ++this.nextInstanceId; // 创建 arkTS 侧的实例 const instance = new RomaInstance( jueName, id.toString(), "", this.napiBridge, param.uiContext, param.abilityContext ) // 给实例绑定状态变革的监听 if (stateListener) { instance.addStateListeners(stateListener); } instance.setPageId(param.pageId); instance.initialize(param.root); this.instanceMap.set(id.toString(), instance); return instance;}2.创建 C++ 环境中的 Instance 实例

在调用 instance.initialize 中通过 N-API调用 crateInstance,准备在 C++ 环境中创建 Instance。
个中第4个参数 (mutations: Mutation[], isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations, isFromCore) } 用来监听 C++ 环境中 Instance 的创建状态。
当后面剖析到 C++ 环境中实例创建完成时,我们在剖析 this.descriptorManager.applyMutations(mutations, isFromCore) 中详细做了什么。
这里只须要知道 ArkTs 中的 Instance 在监听 C++ 中的 Instance 的创建过程。

public initialize(root: NodeContent | null = null) { // 注册实例变革监听器 this.napiBridge.createInstance( this.jueName, this.getId(), root, (mutations: Mutation[], isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations, isFromCore) }, (tag, commandName, args) => { // 省略无关代码 }, (instanceId: string, state: string) => { // 省略无关代码 }) }

通过 N-API 在 C++ 环境中创建对应的实例,并在创建 Instance 的回调中触发从 ArkTS 环境中通报过来的状态监听, auto listener = arkJs.getReferenceValue(listener_ref); arkJs.call<1>(listener, args); 从而将 C++ 中 Instance 创建过程通报到 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++环境中 Instance 状态变革时触发 if (mutations.size() <= 0) { return; } ArkTS arkJs(env); auto napiMutations = mutationsToNapiConverter.convert(env, mutations); std::array<napi_value, 1> args = {napiMutations}; // 获取从 ArkTS 环境中通报过来的监听工具并触发 auto listener = arkJs.getReferenceValue(listener_ref); arkJs.call<1>(listener, args); }, return arkJs.getUndefined();}

在 C++ 环境中创建 Instance 的详细实现如下,记录下从 ArkTs 中获取的状态监听参数 mutationsListener 以便在得当的机遇触发。

void RomaEnv::createInstance(std::string jueName, InstanceId instanceId, ArkUI_NodeContentHandle nodeContentHandle_, MutationsListener mutationsListener, ComponentMethodListener componentMethodListener, StateListener stateListener) { RomaEnv::getInstance().getBackgroundExecutor()([=]() { auto nonConstRef = RomaInstanceManager::getInstance().get(instanceId); // 创建实例,页面 | 模板 RomaInstance::Shared instance = std::make_shared<RomaInstance>(jueName, instanceId); instance->rootInstance_ = instance; instance->attachNativeXComponent(nodeContentHandle_); // 注册监听器 instance->registerInstanceChangeListener(mutationsListener); …… }); }3.创建 JS 环境中的 Instance 实例

下面剖析 JS 端 Instance 的创建过程,接上面 this.romaInstance!.startInstance(this.initialProps).then((result) => {}); 往下看

public async startInstance(initialProps: TObject): Promise<void> { return this.napiBridge.startInstance(this.getId(),initialProps); }

在 ArkTs 端通过 N-API 调用 SDK 中的 startInstance 进入 C++环境

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 = 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 的详细实现void RomaEnv::startInstance(InstanceId instanceId, folly::dynamic &&initialProps, std::function<void()> &&onFinish) { try { RomaEnv::getInstance().getBackgroundExecutor()([=]() { auto nonConstRef = RomaInstanceManager::getInstance().get(instanceId); if (nonConstRef) { // 准备进入 js 环境创建 Instance nonConstRef->start(instanceId, initialProps); this->taskExecutor_->runTask(TaskThread::MAIN, [onFinish]() { onFinish(); }); } }); } catch (const std::exception &e) { throw e.what(); }; }

在 start 方法中开始创建 js 环境下的 Instance,通过 JSI 获取 JS 虚拟机工具 runtime ,调用在 App 启动时就注入到 JS 虚拟机中的 JRPageManager 工具的 createInstance 方法

void RomaInstance::start(SurfaceId surfaceId, folly::dynamic const &initialProps){ // 进入js线程实行实例创建 RomaEnv::getInstance().getRuntimeExecutor()([jueName_ = jueName_, initialProps, surfaceId](jsi::Runtime &runtime) { // 判断 JS 的全局变量中是否有 JRPageManager if (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), }); } } });}

进入 JS 环境后调用 createInstance 方法,开始创建 JS 环境下的 Instance

export function createInstance(bundleName,instanceID,options){ // 判断是否传入实例bundleName以及对应bundle是否已经加载 if (!bundleName || !has_load_bundle(bundleName)) { callLoadBundleJsFileFail(instanceID) return; } JRTransUICore._jr_ydby_new_template_instance(bundleName,instanceID,options);}

在 JS环境中完成 Instance 创建后,调用 _jr_ydby_new_node_instance 开始构建 V-Dom Tree

function _jr_ydby_new_template_instance(template_id, ctx_id, template_data, is_batchCreate) { // 1.根据模板创建JUE实例 var ctx = new JueInstance(ctx_id, template, template_data); ctx.initRootCtxAndStaticCss(); …… // 创建v-dom var v_dom = new _jr_ydby_v_dom(template_id); ctx.v_dom = v_dom; // 2.构建v-dom 对应 root-node var root_node = _jr_ydby_new_node_instance(ctx.c_id, v_dom, template.root_node, null, {}, is_batchCreate); // 构建结束 _jr_ydby_create_finshed(ctx.c_id); return ctx.c_id;}4.在 JS 环境中构建 V-Dom Tree

通过 _jr_ydby_new_node_instance 方法,遍历视图中所有节点及子节点,完玉成部 V-Dom Tree 的创建

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') { // 创建根节点 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{ // 创建其他子节点 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); // 组装数据,天生 _jr_ydby_v_node工具 parent_node.appendChild(current_node, v_dom); // 添加节点 _jr_ydby_add_element(current_node, current_node.node_index); } // 循环创建当前节点下的子节点 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 = att['v-for']; …… // 递归创建子节点 _jr_ydby_new_node_instance(ctx_id, v_dom, sub_t_node, current_node, v_f_ctx, is_batchCreate); } } return current_node;}5.在 C++ 环境中构建 Component Tree

在构建 V-Dom Tree 的过程中,每创建一个节点,都会调用 _jr_ydby_add_element 方法,向宿主C++环境发起创建节点的指令:

_jr_ydby_add_element(current_node, current_node.node_index);// 搜集节点信息var nodePorperty = { template_id: node.template_id, ctx_d: node.ctx_id, tag: node.tag, id: node.id, is_root: node.is_root, type: node.type, parent_node: node.parent_node, style: node.style, cache: node.cache, attr: _jr_ydby_tools_deep_copy(node.attr), value: node.value, event: node.event, index: node.index,//仅cell-slot节点利用 isComponentNode: node.isComponentNode, componentInstanceId: node.componentInstanceId, componentBundleName: node.componentBundleName };// 调用添加方法callAddElement(node.ctx_id, node.parent_node, nodePorperty, index);

在 App 启动时,已向 JS 虚拟机内植入 callAddElement 方法,这里通过 JSI通道将指令从 JS 打通到C++环境

runtime_->global().setProperty( runtime_, "callAddElement", Function::createFromHostFunction( runtime_, PropNameID::forAscii(runtime_, "callAddElement"), 4, [](jsi::Runtime &runtime, jsi::Value const & /thisValue/, jsi::Value const arguments, size_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++环境中吸收到添加指令后,这里开始构建 Component Tree,保存各节点数据信息,并不会同步在界面上添加视图(由于每次利用 N-API在 C++ 和 ArkTS 中通讯,都会因跨措辞和类型转换带来一定的性能损耗),待吸收到 V-Dom Tree 构建完成的后,对应的 Component Tree 的构造也构建完成了,这时再一次性完成视图的绘制。

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; } // 强引用保存节点到父节点的children_数组中 folly::dynamic style = props["style"]; node = RomaNodeFactory::createSharedNode(surfaceId, tag, name, parent_id, index, isComponentNode, componentInstanceId, props["attr"], style, props["event"], props["value"]); size_t childIndex = index; parent->appendChild(node, childIndex); auto shadowView = std::make_shared<ShadowView>(node); // 组件节点,只插入,不创建,等组件实例创建时,再创建 if (!isComponentNode) { // 增加普通节点的创建指令 nonConstRef->rootInstance_.lock()->lastMutations_.push_back( ShadowViewMutation::CreateMutation(shadowView->getSharedShadowView())); } if ((parent->getComponentName() == "document") && nonConstRef->isComponent) { // 如果是子组件中的节点,并且是document的直接子节点 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); } // 增加组件节点的插入指令 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 { // 增加普通节点的插入指令 auto parentShadowView = parent->getShadowView(); nonConstRef->rootInstance_.lock()->lastMutations_.push_back( ShadowViewMutation::InsertMutation(parentShadowView, shadowView->getSharedShadowView(), childIndex)); } // 保存到节点 node->setShadowView(shadowView->getSharedShadowView()); // 弱引用保存节点到实例的map中 nonConstRef->addNode(tag, node); });6. 在 JS 环境中发送节点创建完成的

V-Dom Tree 构建完成后,会调用 _jr_ydby_create_finshed

export function _jr_ydby_create_finshed(ctx_id) { let instance = getInstanceById(ctx_id); callCreateFinish(ctx_id,{template_id:instance.template_id,version:instance.currentVersion});}

同样在动态化 SDK 初始化阶段,已向 JS虚拟机中植入callCreateFinish方法,当 JS 端调用callCreateFinish时,通过 JSI 通道将数据通报到 C++环境中。

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++环境中收到创建完成的后,实行 yoga 布局,获取视图尺寸并调用在 Instance 创建过程中预置的状态监听 mutationsListener 方法。

void UIManager::callCreateFinish(SurfaceId surfaceId) { RomaEnv::getInstance().getBackgroundExecutor()([=]() { auto nonConstRef = RomaInstanceManager::getInstance().get(surfaceId); if (nonConstRef && !nonConstRef->isComponent) { // 实行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 渲染 ,并触发 mutationsListener 监听 if (nonConstRef->rootInstance_.lock()) { auto a = nonConstRef->rootInstance_.lock()->m_mutationsToNapiConverter; nonConstRef->rootInstance_.lock()->mutationsListener(a, mutableList,true); } nonConstRef->createFinish(); }); } } else if (nonConstRef) { RomaEnv::getInstance().taskExecutor_->runTask( TaskThread::MAIN, [nonConstRef, surfaceId] { nonConstRef->createFinish(); }); } });}

下面我们详细剖析一下在触发 mutationsListener 后,都发生了什么?通过 C++ 的转发,终极在 ArkTS 环境中触发如下回调(这是在 ArkTS创建 Instance 时就通过 N-API传入到 C++ 环境的参数之一,上面有先容)

(mutations: Mutation[], isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations, isFromCore) }7.在 ArkTS 环境中构建 Render Tree

实例创建完成后触发 applyMutations 方法,将各节点数据都保存到 descriptor 中。

public applyMutations(mutations: Mutation[], isFromCore: boolean) { // 去重 const tags = mutations.flatMap(mutation => this.applyMutation(mutation, isFromCore)); const tags = new Set(tags); // 遍历各节点 tags.forEach(tag => { // 取实例id,和tag const strArr: string[] = tag.split("##"); const instanceId = strArr[0];//实例id const nodeId = strArr[1];//节点id if(instanceId === this.romaInstance.getId()){ // 更新节点 let updatedDescriptor = this.getDescriptor(nodeId); if(!updatedDescriptor) return; // 在当前实例中更新tag组件的UI描述信息 this.descriptorListenersSetByTag.get(nodeId)?.forEach(cb => { onDescriptorChange(cb, updatedDescriptor); }); }else { // 创建节点 const instance: RomaInstance = RomaEnv.getRomaInstanceManager()?.getInstance(instanceId) as RomaInstance; let updatedDescriptor = instance?.getDescriptor(nodeId); if(!updatedDescriptor) return; instance.refreshComponentUI(nodeId, updatedDescriptor); } });}

在 refreshComponentUI 方法中会触发当前节点对应标签的数据变革监听

public refreshComponentUI(tag: Tag, d: Descriptor) { this.getDescriptorListenersSet(tag)?.forEach(cb => { onDescriptorChange(cb,d); });}

如果标签是 image ,则触发 image 标签的在 aboutToAppear 中的关于标签数据 descriptor 的监听,从 newDescriptor 中获取标签上所有的数据,包括尺寸数据。

aboutToAppear() { if (!this.componentCtx) { return; } this.componentCtx?.aboutToAppear((newDescriptor) => { this.descriptor = newDescriptor; // 链接自定义alt图方法 this.customAltImage = RomaConfig.instance().getImageAltMethod(); // 触发更新 this.onLoadStart(); this.updateImageSource(); // 处理图片 object-position 模式干系逻辑 this.initObjectPositionHandle() // 解析占位图 this.altSource = this.getImageAlt(); this.hasPlaceHoldImage = this.altSource ? true : false; // 解析背景色 let bgColorStr = RomaStyleParser.getStyleToString('background-color',this.descriptor); …… // 设置tint-color this.getTintColor(); // 是否开启抗锯齿 this.interpolation = this.colorFilter ? ImageInterpolation.High : ImageInterpolation.Low; }, (methodName, args:TAny[]) => { // 注册标签方法 if (methodName === 'loadRef') { this.loadRef(args[0]); } }); }8.根据 Render Tree 绘制视图

到此我们也仅仅是完成了进入动态化页面 RomaInstanceView 视图中的 abountToAppear 方法中的数据准备事情。
完成了三个 Instance 的创建,完成了三棵树的创建以及把所有组件和标签干系数据都保存到对应的 descriptor 中,接下来便是在 build 方法中真正绘制视图了。

public build() { // 根据表述信息,构建 RomaComponentFactory.builder(new RomaComponentParam(this.romaInstance, this.descriptor.tag));}

RomaComponentFactory是所有标签组件的工厂方法,定义如下:

export const RomaComponentFactory: WrappedBuilder<[RomaComponentParam]> = wrapBuilder(RomaComponentFactoryBuilder);

在调用 RomaComponentFactory.builder 时,触发 RomaComponentFactoryBuilder 方法,如下只列出示例中用到的标签的实现,其他的省略了。

param.descriptor 中保存了已经构建好的 Render Tree,包括各节点对应的标签类型和所有的节点数据。

@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); }}

以图片标签为例,会创建 RomaImageView 工具,调用其 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) }}

至此!
动态化终不辱义务,一起逢山开路,遇水架桥,超过层层关隘,破定命所缚,所言终达天听,辅社稷,开盛世太平!
便是说动态化完成了从资源被加载-到数据被层层加工流转-到视图被创建-再到渲染到页面上的全过程。

五、视图更新流程

当点击“更新节点数据”按钮后,图片资源被修正,div视图的背景色也被修正。
比较创建的过程,这个页面中节点数据的更新要大略一些,由于各环境下的 Instance 都已经创建好了,各环境中的通道也打通了,只是各节点Differ(数据比拟)的过程,并将 Differ 结果通过各环境中的通道通报给干系视图节点更新即可。
这里不详细先容详细的过程了,由于数据在三种措辞环境中通报的逻辑是同等的,这里只先容一下 Differ 的逻辑。

1.Differ 事理先容

当业务须要更新视图的时候,会根据新的视图数据重新天生一棵新的 V-Dom Tree,和页面旧的 V-Dom Tree 进行比拟,终极得到须要更新的节点数组,将这个数组同步到 ArkTS 的 Render Tree 的对应的节点,触发相应节点更新。



六、方案总结

目前已利用三个线程确保数据在不同环境中的高效处理,为了避免壅塞 JS 线程和 UI 线程,已将繁芜耗时的功能放到 bg 线程中处理,尽可能的提升了页面绘制效率。
但利用 ArkUI 封装好的视图组件绘制的视图层级比较 Android 和 iOS端多出一倍,且利用N-API通讯会带来一定的性能开销,因此先天性的多做了很多的事情。
为了进一步提升视图渲染和数据通讯的效率,操持接下来将 C-API (鸿蒙供应的一组绘制视图的 C 接口)接入到动态化鸿蒙 SDK 中,在 C++ 环境中就完成视图的绘制,以更直接和高效的办法绘制视图,视图的层级将减少一半,同时省去了跨措辞通讯的干系本钱。
经测试,视图绘制和渲染效率将进一步提升,用户将得到更好的利用体验。

动态化是一个涉及 Android、iOS、Harmony、Web、Java、C/C++、Vue、JavaScript、Node、Webpack、CLang、Ninja 等浩瀚领域的综合办理方案。
我们有各个领域精良的小伙伴共同前行,如果你想深入理解某个领域的详细实现,可在评论区留言随时互换~!

相关文章

介绍百度网盘,云端存储时代的创新先锋

随着互联网技术的飞速发展,云计算已经成为现代生活不可或缺的一部分。而在这其中,百度网盘作为国内领先的云存储服务提供商,以其卓越的性...

SEO优化 2025-01-03 阅读0 评论0

介绍监控屏蔽技术,守护个人隐私的利器

随着科技的发展,监控设备已经深入到我们生活的方方面面。在享受便利的隐私安全问题也日益凸显。如何有效屏蔽监控,保护个人隐私,成为人们...

SEO优化 2025-01-03 阅读0 评论0

介绍番号观看方法,轻松驾驭影视世界

随着互联网的普及,网络影视资源日益丰富,番号作为影视作品的标识码,已经成为广大观众了解、搜索和观看影视作品的重要途径。如何正确地使...

SEO优化 2025-01-03 阅读0 评论0

介绍盗微信号黑幕,网络安全的严峻挑战

在数字化时代,微信已成为人们生活中不可或缺的通讯工具。随着微信用户数量的激增,盗微信号的事件也日益增多。本文将深入剖析盗微信号的方...

SEO优化 2025-01-03 阅读0 评论0