鸿蒙跨端实践-揭秘视图渲染流程

01 前言 理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将 通过本文你将全面清晰的洞悉动态化跨端的实现原理,感受黑悟空(数据)一路打怪升级(在不同语言环境中流转改造),逆天改命(操控原生视图绘制),终成齐天大圣(完成视图渲染呈现)的艰辛历程。
02 原理介绍
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
通过本文你将全面清晰的洞悉动态化跨端的实现原理,感受黑悟空(数据)一路打怪升级(在不同语言环境中流转改造),逆天改命(操控原生视图绘制),终成齐天大圣(完成视图渲染呈现)的艰辛历程。
原理介绍
1.动态化跨端原理介绍
动态化- 罗码(Roma,后文统称动态化)是一个完全自主研发的一站式跨平台解决方案,一份代码,可以在 Android、iOS、Harmony 及 Web 上运行。动态化的跨端原理与 React Native 、Weex 一致,在吸取业界各跨端框架优势的基础上,加上自主创新,打造的一个完全自主可控的综合跨端解决方案。其跨端的理论基础在于各端都具备统一解析和执行 JavaScript 的能力(JS 虚拟机)。业务代码被打包成 js 文件,在各端被 JS 虚拟机加载,被解析成多条不同功能的指令,调用原生宿主能力,完成数据的传递和视图的绘制。示意图如下:
为了在鸿蒙端更好的接入动态化,有几点需要特别说明一下:
2.鸿蒙接入动态化原理介绍
动态化在鸿蒙端的架构示意图如下:
在 JS Engine 环境中通过对视图元素的增删改查操作及其他相关指令构建出了一个组件树(后文统称 V-Dom Tree, 作为一个纯对象,可以清晰的提炼出视图元素的层级结构,这个抽象描述是与平台无关的,因此可在 JS环境中生成 V-Dom Tree),在原生端根据 V-Dom Tree 的结构创建出对应的 Render Tree,再经过布局引擎布局后,就显示出了业务具体的视图效果;由于通过统一接口实现了 JS 和原生的双向通讯,当用户在业务视图上触发了交互事件,或业务方需要改变视图显示的时候,都可通过统一接口顺利的完成事件和数据的交互逻辑以及业务视图的渲染。
业务示例
App启动时第一时间就会加载动态化 SDK,包括 JS Engine(动态化自主研发的为各平台的 JS 虚拟机扩展的功能) 和 Jue Instance (鸿蒙端对 JS Engine Interface 的实现)两部分,完成动态化运行环境的初始化。具体包括业务代码实例管理、任务管理、虚拟Dom树管理、虚拟Dom树 Differ、业务页面渲染管理、生命周期管理、事件分发、业务逻辑处理等一系列功能。
为了适配不同系统,通过制定统一的对外接口规范(JS Engine Interface)让不同平台按照标准化对接及扩展(比如这次华为鸿蒙系统的适配只需实现统一对外接口即可),接口包含了实例创建、生命周期、元素的增删改查、JS和原生的双向通讯、热重载交互等各种能力,各端按需实现。
鸿蒙系统上,Jue Instance 具体实现接口声明的方法,创建动态化页面和处理业务交互逻辑。提供内置基础标签和模块,同时打通了特殊业务场景下的标签和模块的扩展能力,业务可根据具体的业务场景扩充和完善。
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;"><textstyle="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 文件,从而开始视图绘制。
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
1.绘制原理
在鸿蒙端,进入动态化页面之前,确保资源文件(ADemo.js)已被加载到内存中后,由原生端(ArkTS)发起动态化实例的创建,调用动态化 SDK 提供的创建动态化实例(Instance)的方法,通过 N-API 调用宿主环境(C++)的具体实现,创建 C++ 环境下的 Instance。然后通过 oo (将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。
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 = truereturn 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;
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();}
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);}
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 环境创建 InstancenonConstRef->start(instanceId, initialProps);this->taskExecutor_->runTask(TaskThread::MAIN, [onFinish]() {onFinish();});}});} catch (const std::exception &e) {throw e.what();};}
void RomaInstance::start(SurfaceId surfaceId, folly::dynamic const &initialProps){// 进入js线程执行实例创建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),});}}});}
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);}
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-domvar v_dom = new _jr_ydby_v_dom(template_id);ctx.v_dom = v_dom;// 2.构建v-dom 对应 root-nodevar 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);
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();}));
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 环境中发送节点创建完成的消息
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();}));
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(); });}});}
(mutations: Mutation[], isFromCore: boolean) => { this.descriptorManager.applyMutations(mutations, isFromCore) }7)在 ArkTS 环境中构建 Render Tree
public applyMutations(mutations: Mutation[], isFromCore: boolean) {// 去重const tags = mutations.flatMap(mutation => this.applyMutation(mutation, isFromCore));const tags = new Set(tags);// 遍历各节点tags.forEach(tag => {// 取实例id,和tagconst strArr: string[] = tag.split("##");const instanceId = strArr[0];//实例idconst nodeId = strArr[1];//节点idif(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);}});}
public refreshComponentUI(tag: Tag, d: Descriptor) {this.getDescriptorListenersSet(tag)?.forEach(cb => {onDescriptorChange(cb,d);});}
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-colorthis.getTintColor();// 是否开启抗锯齿this.interpolation = this.colorFilter ? ImageInterpolation.High : ImageInterpolation.Low;}, (methodName, args:TAny[]) => {// 注册标签方法if (methodName === 'loadRef') {this.loadRef(args[0]);}});}
8)根据 Render Tree 绘制视图
public build() {// 根据表述信息,构建RomaComponentFactory.builder(new RomaComponentParam(this.romaInstance, this.descriptor.tag));}
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);}}
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 的逻辑。
Differ 原理介绍
动态化是一个涉及 Android、iOS、Harmony、Web、Java、C/C++、Vue、JavaScript、Node、Webpack、CLang、Ninja 等众多领域的综合解决方案。我们有各个领域优秀的小伙伴共同前行,如果你想深入了解某个领域的具体实现,可在评论区留言/后台回复【技术交流】加入技术交流群,随时探讨交流~!
▪功能支撑:会员成长体系、等级计算策略、权益体系、营销底层能力支持
▪用户活跃:会员关怀、用户触达、活跃活动、业务线交叉获客、拉新促活