Dynamic Cross‑Platform Rendering Architecture and Implementation on HarmonyOS
This article explains the end‑to‑end architecture, principles, and code implementation of the dynamic cross‑platform framework (Roma/Dynamic) on HarmonyOS, covering template loading, JS‑C++‑ArkTS instance creation, V‑Dom and Component tree construction, rendering pipeline, view updates, and performance optimizations.
The article begins with an overview of the dynamic cross‑platform solution (referred to as "Dynamic" or "Roma"), describing how a single codebase can run on Android, iOS, HarmonyOS, and Web by leveraging a unified JavaScript virtual machine and a template‑driven rendering engine.
Principle Introduction
Dynamic follows the same principles as React Native and Weex: a template is fetched (or cached), parsed into a view‑tree structure, expressions are evaluated, and custom events are bound before the view is finally rendered. The process relies on a JS engine, an expression engine, and an event‑parsing engine.
HarmonyOS Integration
Because HarmonyOS’s ArkTS VM cannot directly load JavaScript files, the team ported V8 to the platform and obtained a JSVM‑API from Huawei. A native static library libRomaSdk.so is provided; integrating it into a HarmonyOS project enables the dynamic rendering capabilities.
The architecture diagram shows that at app start the SDK loads the JS engine and a Jue Instance , initializing the runtime and preparing instance management, task handling, and virtual‑DOM management.
Business Example Code
<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" }; },
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>The article then shows how the ADemo.jue file is bundled into ADemo.zip , delivered to the device, unpacked, and finally loaded by the native JS engine.
Resource Loading Flow
After the resource is unpacked, the app creates a RomaInstance on three language layers (ArkTS, C++, JS). Each layer registers listeners, creates its own instance, and links them via N‑API callbacks.
Instance Creation Details
In ArkTS, createInstance builds the native instance and attaches a state listener. In C++, createInstance registers a mutation listener that forwards changes back to ArkTS. In JS, startInstance invokes the JRPageManager.createInstance function, which builds the 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) {
// recursively creates V‑Dom nodes and attaches attributes, styles, events, etc.
}During V‑Dom construction, each node triggers callAddElement (a host function injected into the JS runtime) which sends a command to the C++ side to create a corresponding component node.
Component Tree Construction
The C++ side receives the callAddElement command, creates a RomaNode , wraps it in a ShadowView , and records a mutation. After the whole V‑Dom is built, _jr_ydby_create_finshed notifies the native side, which runs Yoga layout, generates layout mutations, and finally calls the registered mutationsListener to update the ArkTS render tree.
Render Tree and UI Update
ArkTS applies mutations via applyMutations , updates descriptors, and notifies UI listeners. For example, an image tag’s descriptor change triggers image loading, background‑color parsing, and rendering through the RomaImageView builder.
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(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))
.onError(event => this.dispatchOnError(event))
.position(this.imgPosition)
.clipShape(this.imgClipShape);
}
}The article concludes with a performance summary: three threads (UI, JS, background) keep processing efficient, but the extra view hierarchy on HarmonyOS and N‑API overhead introduce latency. Future work includes adopting Huawei’s C‑API to render directly in C++ and halve the view hierarchy.
Finally, the article invites readers to join technical discussion groups for deeper dives into any of the involved technologies (Android, iOS, HarmonyOS, JavaScript, C/C++, Vue, Webpack, etc.).
JD Tech
Official JD technology sharing platform. All the cutting‑edge JD tech, innovative insights, and open‑source solutions you’re looking for, all in one place.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.