分享发现 万字深入 HarmonyOS ACE UI 框架解析,带你看懂 UI 渲染流程

yuanguanhua(智能互联) · 2021年08月03日 · 58 次阅读

作者:zhiqiang、sunfei、wanglei,华为软件开发工程师

UI 框架简介以及业界发展趋势

UI,即用户界面,主要包含视觉(比如图像、文字、动画等可视化内容)以及交互 (比如按钮点击、列表滑动、图片缩放等用户操作)。UI 框架,则是为开发 UI 而提供的基础设施,比如视图布局,UI 组件,事件响应机制等。

从操作系统平台支持方式来看,UI 框架一般可分为原生 UI 框架跨平台 UI 框架两种。

1.原生 UI 框架。这个一般是指操作系统自带的 UI 框架,典型的例子包括 iOS 的 UI Kit,Android 的 View 框架等。这些 UI 框架和操作系统深度绑定,一般只能运行在相应的操作系统上。功能,性能,开发调测等方面和相应的操作系统结合较好。

2.跨平台 UI 框架。这个一般是指可以在不同的平台(OS)上运行的独立的 UI 框架。典型例子包括 HTML5 以及基于 HTML5 延伸出来的前端框架 React Native, 以及 Google 的 Flutter 等。 跨平台 UI 框架的目标是代码只需一次编写,经过少量修改甚至不修改,可以部署到不同的操作系统平台上。当然,实现跨平台也是有代价的,由于不同平台存在差异性(比如 UI 的呈现方式差异,API 差异等等),导致 UI 框架本身的架构实现,以及和不同平台的融合都有不小的挑战。

从编程方式上来看,UI 框架一般可分为命令式 UI 框架声明式 UI 框架两种:

1.命令式 UI 框架。过程导向 - 告诉 “机器” 具体步骤,命令 “机器” 按照指定步骤去做。比如 Android 原生 UI 框架(View 框架)或 iOS 的 UIKit,提供了一系列的 API 让开发者直接操控 UI 组件 - 比如定位到某个指定 UI 组件,进行属性变更等。这种方式的优点是开发者可以控制具体的实现路径,经验丰富的开发者能够写出较为高效的实现。不过这种情况下,开发者需了解大量的 API 细节并指定好具体的执行路径,开发门槛较高。具体的实现效果上,也高度依赖开发者本身的开发技能。另外,由于和具体实现绑定较紧,在跨设备情况下,灵活性和扩展性相对有限。

2.声明式 UI 框架。结果导向 - 告诉 “机器” 你需要什么,机器负责怎么去做。比如 Web 前端框架 Vue,或 iOS 的 SwiftUI 等,框架会根据声明式语法的描述,渲染出相应的 UI,同时结合相应编程模型,框架会根据数据的变化来自动更新相应的 UI。

这种方式的优点是开发者只需描述好结果,相应的实现和优化由框架来处理。另外,由于结果描述和具体实现分离,实现方式相对灵活同时容易扩展。不过这种情况下,对框架的要求较高,需要框架有完备的直观的描述能力并能够针对相应的描述信息实现高效的处理。

UI 框架是应用开发的核心组成部分。纵观业界 UI 框架,其主要发展趋势表现为:

1.从命令式 UI 往声明式 UI 发展

比如 iOS 中的 UIKit 到 SwiftUI, Android 中的 View 到 Jetpack Compose。

这样可以实现更加直观便捷的 UI 开发。

2.UI 框架和语言运行时深度融合

SwiftUI,Jetpack Compose, Flutter 都利用了各自的语言特性 - 比如在 UI 描述方面,SwiftUI 中的 Swift 语言,Jetpack Compose 中的 Kotlin 语言都精简了 UI 描述语法;在性能方面, Swift 通过引入轻量化结构体等语言特性更好的实现内存快速分配和释放,Flutter 中 Dart 语言则在运行时专门针对小对象内存管理做相应优化等。

3.跨平台(OS)能力

跨平台(OS)能力可以让一套代码复用到不同的 OS 上,主要是为了提升开发效率,降低开发成本。不过这里面也有一系列的挑战,比如运行在不同平台上的性能问题,能力和渲染效果的一致性问题等。业界在这方面也是不断的演进,主要有几种方式:

1.JS/Web 方案,比如 HTML5 利用 JS/Web 的标准化生态,通过相应的 Web 引擎实现跨平台目标;

2.JS+Native 混合方式,比如 React Native、Weex 等,结合 JS 桥接到原生 UI 组件的方式实现了一套应用代码能够运行到不同 OS 上;

3.平台无关的 UI 自绘制能力 + 新的语言,比如 Flutter,整个 UI 基于底层画布由框架层来绘制,同时结合 Dart 语言实现完整的 UI 框架。Flutter 从设计之初就是将跨平台能力作为重要的竞争力去吸引更多的开发者。

另外,有趣的是,部分原生开发框架也开始往跨平台演进。比如,Android 原生的开发框架 Jetpack Compose 也开始将跨 OS 支持作为其中的目标,计划将 Compose 拓展到桌面平台,比如 Windows,MacOS 等。

此外,随着智能设备的普及,多设备场景下,设备的形态差异(屏幕大小、分辨率,形状, 交互模式等),以及设备的能力差异(从百 K 级内存到 G 级内存设备等),以及应用需要在不同设备间协同,这些都对 UI 框架以及应用开发带来了新的挑战。

ACE UI 框架是什么

ACE 全称是 Ability Cross-platform Environment (元能力跨平台执行环境)。是华为设计的应用在 HarmonyOS 上的 UI 框架。ACE UI 框架结合 HarmonyOS 的基础运行单元 Ability,语言和运行时,以及各种平台(OS)能力 API 等共同构成 HarmonyOS 应用开发的基础,实现了跨设备分布式调度,以及原子化服务免安装等能力。

ACE 提供两种开发语言以供不同开发者进行选择,分别为 Java 语言和 JavaScript 语言,其中 Java 仅支持在内存较大的设备上使用如大屏、手机、平板等设备使用,而 JavaScript 支持在百 K 级到 G 级设备上使用。

在多设备场景下,由于不同的设备形态以及设备能力的巨大差异,目前业界还没有任何一个 UI 框架能够较好的解决相应的问题。

所以ACE UI 框架的整体设计思路是:

1.建立分层机制,引入高效的 UI 基础后端,并能够与 OS 平台解耦,形成一致化的 UI 体验 2.通过多前端的方式扩展应用生态,并结合声明式 UI,在开发效率上持续演进 3.框架层统一结合语言以及运行时,分布式,组件化设计等,围绕跨设备,进一步提升体验

ACE 将应用的 UI 界面进行解析,通过创建后端具体 UI 组件、进行布局计算、资源加载等处理后生成具体绘制指令,并将绘制命令发送给渲染引擎,渲染引擎将绘制指令转换为具体屏幕像素,最终通过显示设备将应用程序转换为可见的界面效果展示给用户。

ACE UI 框架的整体架构如下图所示,主要由前端框架层、桥接层、引擎层和平台抽象层四大部分组成,下面我们一一介绍。

1.前端框架层

该层主要包括相应的开发范式(比如主流的类 Web 开发范式),组件/API,以及编程模型MVVM(Model-View-ViewModel)。其中 Model 是数据模型层,代表了从数据源读取到的数据;View 是视图 UI 层,通过一定的形式把系统中的数据向用户呈现出来;ViewModel: 视图模型层,是数据和视图之间的桥梁。它双向绑定了视图和数据,使得数据的变更能够及时在视图上呈现,用户在视图上的修改也能够及时传递给后台数据,从而实现数据驱动的 UI 自动变更。

开发范式可以扩展,来支持生态发展。不同的开发范式可以统一适配到底层的引擎层

2.桥接层

该层主要是作为一个中间层,实现前端开发范式到底层引擎(包括 UI 后端,语言&运行时)的对接

3.引擎层

该层主要包含两部分:UI 后端引擎和语言执行引擎。

1.由 C++ 构建的UI 后端引擎,包括 UI 组件、布局视图、动画事件、自绘制渲染管线和渲染引擎 。

在渲染方面,我们尽可能把这部分组件设计得小而灵活。这样的设计,为不同前端框架提供灵活的 UI 能力,这部分通过 C++ 组件组合而成。通过底层组件的按需组合,布局计算和渲染并行化,并结合上层开发范式实现了视图变化最小化的局部更新机制,从而实现高效的 UI 渲染。

除此之外,引擎层还提供了组件的渲染管线、动画、主题、事件处理等基础能力。目前复用了 Flutter 引擎提供基础的图形渲染能力、字体管理、文字排版等能力,底层使用 Skia 或其他图形库实现,并通过 OpenGL 实现 GPU 硬件渲染加速

在多设备 UI 适配方面,通过多种原子化布局能力(自动折行、隐藏、等比缩放等),多态 UI 控件(描述统一,表现形式多样),以及统一交互框架(不同的交互方式归一到统一的事件处理)来满足不同设备的形态差异化需求。

另外,引擎层也包含了能力扩展基础设施,来实现自定义组件以及系统 API 的能力扩展

2.语言&运行时执行引擎。可根据需要切换到不同的运行时执行引擎,满足不同设备的能力差异化需求

4.平台抽象层

该层主要是通过平台抽象,将平台依赖聚焦到底层画布,通用线程以及事件机制等少数必要的接口上,为跨平台打造了相应的基础设施,并能够实现一致化 UI 渲染体验。

相应的,配套的开发者工具(HUAWEI DevEco Studio)结合 ACE UI 的跨平台渲染基础设施,以及自适应渲染,可做到和设备比较一致的渲染体验以及多设备上的 UI 实时预览。

另外,ACE UI 框架还设计了可伸缩的架构,即前端框架、语言运行时、UI 后端等都做了解耦,可以有不同的实现。这样就具备可部署到百 K 级内存的轻量级设备的能力,如下所示:

在 ACE UI 的轻量化实现中,通过前端框架核心下沉 C++ 化,减小 JS 部分的内存占用,使用 C++ 进行更为严格的内存分配与管理,并且采用更为轻量的 JS 引擎,UI 部分采用轻量的 UIKit 并结合轻量图形引擎,达到内存非常轻量占用的目标。接口能力保证是全量能力的子集,这样可以保证轻量化设备上可执行的应用,可以在更高等级的设备上执行,而无需重新开发。这也就是采用 ACE JS 开发范式的优势所在,采用统一的开发范式进行应用开发后,开发者无需关心具体运行时的前端框架、JS 引擎与后端 UI 组件,根据运行平台不同,采用最佳的模块,保障了应用在不同平台都可具有最佳的运行性能。不过由于轻量级设备上的资源限制, 所支持的 API 能力相对有限,但公共部分的 API 是完全共通的。

综上所述,ACE UI 框架具备如下特点:

1.支持主流的语言生态 – JavaScript 2.支持类 Web 开发范式, MVVM 机制。并在架构上可支持多前端开发范式,进一步简化开发 3.通过统一的 UI 后端,实现高性能以及跨平台一致化的渲染体验 4.通过多态 UI、原子化布局、统一交互,以及可伸缩的运行时设计,进一步降低不同设备形态下的 UI 开发门槛,并能够通过统一的开发范式,实现一套代码跨设备部署(覆盖百 K 级到 G 级内存设备)

ACE UI 框架渲染流程解析

接下来我们通过一个手机侧 ACE JS 应用渲染流程的完整流程来介绍 ACE UI 框架的具体渲染技术。

1)线程模型

ACE JS 应用启动时会创建一系列线程,形成独立的线程模型,以实现高性能的渲染流程。

每个 ACE JS 应用的进程,包含唯一一个 Platform 线程和若干后台线程组成的异步任务线程池:

•Platform 线程:当前平台的主线程,也就是应用的主线程,主要负责平台层的交互、应用生命周期以及窗口环境的创建 •后台线程池:一系列后台任务,用于一些低优先级的可并行异步任务,如网络请求、Asset 资源加载等。除此之外,每个实例还包括一系列专有线程 •JS 线程:JS 前端框架的执行线程,应用的 JS 逻辑以及应用 UI 界面的解析构建都在该线程执行 •UI 线程:引擎的核心线程,组件树的构建以及整个渲染管线的核心逻辑都在该线程:包括渲染树的构建、布局、绘制以及动画调度 •GPU 线程:现代的渲染引擎,为了充分发挥硬件性能,都支持 GPU 硬件加速,在该线程上,会通过系统的窗口句柄,创建 GPU 加速的 OpenGL 环境,负责将整个渲染树的内容光栅化,直接将每一帧的内容渲染合成到该窗口的 Surface 上并送显 •IO 线程:主要为了异步的文件 IO 读写,同时该线程会创建一个离屏的 GL 环境,这个环境和 GPU 线程的 GL 环境是同一个共享组,可以共享资源,图片资源解码的内容可直接在该线程上传生成 GPU 纹理,实现更高效的图片渲染

每个线程的作用,在后续的渲染流程中还会进一步提到。

2)前端脚本解析

ACE UI 框架支持不同的开发范式,可以对接到不同的前端框架上。

以类 Web 开发范式为例,开发者开发的应用,通过开发工具链的编译,会生成引擎可执行的 Bundle 文件。应用启动时,会将 Bundle 文件在 JS 线程上进行加载,并且将该内容作为输入,供 JS 引擎进行解析执行,最终生成前端组件的结构化描述,并建立数据绑定关系。例如包含若干简单文本的应用会生成类似下图的树形结构,每个组件节点会包含该节点的属性及样式信息。

3)渲染管线构建

如上图,前端框架的解析后,根据具体的组件规范定义向前端框架对接层请求创建 ACE 渲染引擎提供的组件。

前端框架对接层通过 ACE 引擎层提供的 Component 组件实现前端组件定义的能力。Component 是一个由 C++ 实现的 UI 组件的声明式描述,描述了 UI 组件的属性及样式,用于生成组件的实体元素。每一个前端组件会对接到一个 Composed Component,表示一个组合型的 UI 组件,通过不同的子 Component 组合,构造出前端对应的 Composed 组件。每个 Composed 组件是前后端对接的一个基础的更新单位。

以上面的前端组件树为例,每个节点会使用一组 Composed 组件进行组合描述,对应关系如下图,该对应关系只是一个示例,实际场景的对应关系可能会更复杂。

有了每个前端节点对应的 Component,就形成了一个完成 Page 的描述结构,通知渲染管线挂载新的页面。

在 Page 挂载之前,渲染管线已经提前创建了几个关键的核心结构,Element 树和 Render 树:

Element 树,Element 是 Component 的实例,表示一个具体的组件节点,它形成的 Element 树负责维持界面在整个运行时的树形结构,方便计算局部更新算法。另外对于一些复杂的组件,在该数据结构上会实现一些对子组件逻辑上的管理。

Render 树,对于每个可显示的 Element 都会为其创建对应的 RenderNode,它负责一个节点的显示信息,它形成的 Render 树维护着整个界面的渲染需要用到的信息,包括它的位置、大小、绘制命令等,界面后续的布局、绘制都是在 Render 树上进行的。

当应用启动时,最初形成的 Element 树只有几个基础的几节点,一般包括 root、overlay、stage,分别作用如下:

RootElement:Element 树的根节点,仅仅负责全局背景色的绘制

OverlayElement:一个全局的悬浮层容器,用于弹窗等全局绘制场景的管理

StageElement:一个 Stack 容器,作为全局的 “舞台”,每个加载完成的页面都要挂载到这个 “舞台” 下,它管理应用的多个页面之间的转场动效等。

在 Element 树创建的过程中,也会同步的把 Render 树也创建起来,初始状态如下图:

当前端框架对接层通知渲染管线准备好了页面,在下一个帧同步信号(VSync)到来时,就会在渲染管线上进行页面的挂载,具体流程就是通过 Component 来实例化生成 Element 的过程,创建成功的 Element 同步创建对应的 RenderNode:

如上图所示,目标要将整个 Page 的 Component 描述挂载到 StageElement 上,如果当前 Stage 下还未有任何 Element 节点,就会递归逐个节点生成 Component 对应的 Element 节点。对于组合类型的 ComposedElement,则同时会把 Element 的引用记录到一个 Composed Map 中,方便后续更新时快速查找。对于可见类型的容器节点或渲染节点,则会创建对应的 RenderNode,并挂在 Render 树上。

当生成了当前页面的 Element 树和 Render 树,页面渲染构建的完整过程就结束了。

4)布局绘制机制

接下来就进入了布局和绘制的阶段,布局和绘制都是在 Render 树上进行的。每个 RenderNode 都会实现自己的布局算法和绘制方法。

布局

布局的过程就是通过各类布局的算法计算出每个 RenderNode 在相对空间上的真实大小和位置。

如下图所示,当某个节点的内容发生变化时,就会标记自己为 needLayout,并一直向上标记到布局边界(ReLayout Boundary),布局边界是重新布局的一个范围标记,一般情况下,如果一个节点的布局参数信息(LayoutParam)是强约束的,例如它布局期望的最大尺寸和最小尺寸是相同的,那么它就可以作为一个布局边界。布局是个深度优先遍历的过程。从布局边界开始,父节点自顶向下将 LayoutParam 传给子节点,子节点自底向上据此计算得到尺寸大小和位置,

对于每个节点来说,布局分为三个步骤:

1.当前节点递归调用子节点的 layout 方法,并传递布局的参数信息(LayoutParam),包含了布局期望的最大尺寸和最小尺寸等 2.子节点根据布局参数信息,使用自己定义的布局算法来计算自己的尺寸大小 3.当前节点获取子节点布局后的大小,再根据自己的布局算法来计算每个子节点的位置信息,并将相对位置设置给子节点保存

根据上述的流程,一次布局遍历完成后,每个节点的大小和位置就都计算出来了,可以进行下一步的绘制。

绘制

同布局一样,绘制也是一个深度遍历的过程,遍历调用每个 RenderNode 的 Paint 方法,此时的绘制只是根据布局算出来的大小和位置,在当前绘制的上下文记录每个节点的绘制命令。

为什么是记录命令,而不是直接绘制渲染呢?在现代的渲染引擎中,为了充分使用 GPU 硬件加速的能力,一般都会使用 DisplayList 的机制,绘制过程中仅仅将绘制的命令记录下来,在 GPU 渲染的时候统一转成 OpenGL 的指令执行,能最大限度的提高图形的处理效率。所以在上面提到的绘制上下文中,会提供一个可以记录绘制命令的画布(Canvas)。每一个独立的绘制上下文可以看作是一个图层。

为了提高性能,这里引入了图层(Layer)的概念。通常绘制会将渲染内容分为多个层进行加速。对于会频繁变化的内容,将其单独创建一个图层,那么这个独立图层的频繁刷新就不必导致其他内容重新绘制,从而达到提升性能并减少功耗的效果,同时还可以支持 GPU 缓存等优化。每个 RenderNode 都可以决定自己是否需要单独分层。

如下图所示,绘制流程会从需要绘制的节点中,挑选最近的且需要分层的节点开始,自顶向下的执行每个节点的 Paint 方法。

对每个节点,绘制分为四个步骤:

1.如果当前节点需要分层,那么需要创建一个新的绘制上下文,并提供可以记录绘制命令的画布 2.在当前的画布上记录背景的绘制命令 3.递归调用子节点的绘制方法,记录子节点的绘制命令 4.在当前的画布上记录前景的绘制命令

一次完整的绘制流程结束后,我们会得到一棵完整的 Layer 树,Layer 树上包含了这一帧完整的绘制信息:包括每一层的位置、transform 信息、Clip 信息、以及每个元素的绘制命令。下一步就要经过光栅化和合成的过程,将这一帧的内容显示到界面。

5)光栅化合成机制

在上面的绘制流程结束后,会通知 GPU 线程开始进行合成的流程。

如上图所示,UI 线程(UI Thread)在渲染管线中的输出是 LayerTree,它相当于一个生产者,将生产的 LayerTree 添加到渲染队列中。GPU 线程(GPU Thread)的合成器(Compositor)相当于消费者,每个新的渲染周期中,合成器会从渲染队列中获取一个 LayerTree 进行合成消费。

对于需要缓存的 Layer,还要执行光栅化生成 GPU 纹理,所谓光栅化就是将 Layer 里面记录的命令进行回放,生成每个实体的像素的过程。像素是存储在纹理的图形内存中。

合成器会从系统的窗口中获取当前的 Surface,将每个 Layer 生成的纹理进行合成,最终合成到当前 Surface 的图形内存(Graphic Buffer)中。这块内存中存储的就是当前帧的渲染结果内容。最终还需要将渲染结果提交到系统合成器中合成显示。系统的合成过程如下图所示:

当 GPU 线程的合成器完成一帧的合成后,会进行一次 SwapBuffer 的操作,将生成的 Graphic Buffer 提交到与系统合成器建立的帧缓冲队列(Buffer Queue)中。系统合成器会从各个生产端获取最新的内容进行最终的合成,以上图为例,系统合成器会将当前应用的内容和系统其它的显示内容,例如 System UI 的状态栏、导航栏,进行一次合成,最终写入到屏幕对应的帧缓冲区(Frame Buffer)中。液晶屏的驱动就会从缓冲区读取内容进行屏幕的刷新,最终将内容显示到屏幕上。

6)局部更新机制

经过上面 1~5 的流程,完成了首次完整的渲染的流程,在后续的运行中,例如用户输入、动画、数据改变都有可能造成页面的刷新,如果只是部分元素发生了变化,并不需要全局的刷新,只需要启动局部更新即可。那么局部更新是怎么做到的?下面我们介绍一下局部 更新的流程。

以上图为例,JS 在代码中更新了数据,通过数据绑定模块会自动触发前端组件属性的更新,然后通过 JS 引擎异步发起更新属性的请求。前端组件会根据变更的属性,构建一组新的 Composed 的补丁 (Patch),作为渲染管线更新的输入。

如上图所示,在下一个 VSync 到来时,渲染管线会在 UI 线程开始更新的流程。通过 Composed 补丁的 Id,在 ComposedMap 中查询到对应的 ComposedElement 在 Element 树上的位置。通过补丁对 Element 树进行更新。以 ComposedElement 为起始,逐层进行对比,如果节点类型一致则直接更新对应属性和对应的 RenderNode,如果不一致则重新创建新的 Element 和 RenderNode。并将相关的 RenderNode 标记为 needLayout 和 needRender。

如上图所示,根据标记需要重新布局和重新渲染的 RenderNode,从最近的布局边界和绘制图层进行布局和绘制的流程,生成新的 Layer 树,只需要重新生成变更 RenderNode 对应的 Layer 即可。

如上图所示,接下来,根据刷新后的 Layer 树作为输入,在 GPU 线程进行光栅化和合成。对于已经缓存的 Layer 则不需要重新光栅化,合成器只需要将已缓存的 Layer 和未缓存或更新的 Layer 重新合成即可。最终经过系统合成器的合成,就会将新一帧的内容显示。

以上就是一个 ACE JS 应用的渲染及更新的流程。最后,通过两张流程图回顾一下整体的流程:

了解完 ACE JS 应用的渲染及更新的流程,如果大家想了解更多关于 HarmonyOS UI 框架如何解决设备形态差异带来的开发挑战和应用示例,可参考我们之前推出的内容:解密 HarmonyOS UI 框架:

https://mp.weixin.qq.com/s/0RZL09vKppIZmpqTJUeRSA

ACE UI 框架目前的成熟度以及演进

截至目前,ACE UI 框架已商用落地了华为运动手表,华为智能手表,华为智慧屏,华为手机,华为平板等一系列产品。使用场景包括日历、出行、健身、实用工具等各类应用,手机 - 设备碰一碰全品类的应用,以及今年六月份发布的 HarmonyOS 中各类的服务卡片 - 图库、相机等。另外,在开发调测方面,开发者工具(HUAWEI DevEco Studio)中也集成了 ACE UI 框架,支持在 PC 端(MacOS,Windows)上的开发调测,实时预览(包括实时多设备预览,组件级预览,双向预览等),实现了在 PC 上和设备上一致的渲染体验。

未来,面向开发者的极简开发,面向消费者的流畅酷炫的体验,以及能够高效在不同设备不同平台上部署,ACE UI 框架会继续沿着精简开发和高性能两个方面演进,结合语言更进一步简化开发范式,结合运行时在跨语言交互,类型优化等方面进一步增强性能体验,结合分布式能力将编程模型从 MVVM 演进到分布式 MVVM(Distributed Model-View-ViewModel)等。采用类自然语言的声明式 UI 描述进行界面搭建,编程语言也进一步开放,未来考虑向 TS 进行演进,从动效、布局和性能方面进一步提升用户使用体验。

当然,应用生态还会涉及更多的方面,比如三方插件的繁荣,跨 OS 平台的扩展,更具创新的分布式体验等等。ACE UI 框架还很年轻,期待和众多开发者一起,重点围绕着多设备组成的超级终端的新兴场景,不断打磨完善,共同构建领先的应用体验和生态!

目前 HarmonyOS 的线上开发体验已上架,欢迎大家在线体验。ACE JS 框架已经进驻开源社区,欢迎关注与共建,期待诸位一起共建我们的开发框架,有开发过程中的疑问和对 HarmonyOS 开发的好的建议欢迎登录论坛,我们一起探讨。论坛链接可访问:

https://developer.huawei.com/consumer/cn/forum/

开源社区:https://gitee.com/openharmony/ace_ace_engine/

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号