介绍
XComponent 提供了应用在 native 侧调用 OpenGLES 图形接口的能力,本文主要介绍如何配合 Vsync 事件,完成自定义动画。在这种实现方式下,自定义动画的绘制不在 UI 主线程中完成,即使主线程卡顿,动画效果也不会受影响。
XComponent 的核心功能
- OpenGL ES 接口调用 :
提供在应用 **Native 层(C/C++) **直接调用 OpenGL ES 图形接口的能力,绕过上层 UI 框架(如 ArkUI),实现高性能图形渲染(如 3D 图形、特效、游戏等)。
脱离 UI 主线程:
图形渲染在独立线程中执行,与 UI 主线程解耦,避免主线程卡顿影响图形性能。
2. 配合 Vsync 事件的动画实现
- Vsync(垂直同步) :
显示系统的硬件信号,指示屏幕刷新的时间点(通常 60Hz/120Hz)。XComponent 监听 Vsync 事件,确保动画帧率与屏幕刷新率同步,避免画面撕裂或卡顿。
- 自定义动画流程 :
- Native 层绘制 :通过 OpenGL ES 在 XComponent 的独立线程中渲染每一帧动画。
- Vsync 驱动 :收到 Vsync 信号后提交新帧到屏幕,保证流畅性。
- 主线程无关 :即使主线程因复杂布局或逻辑阻塞,动画仍能持续流畅运行。
先看看这个项目:



这是这个项目的结构,文件。
这是一个非常典型的华为鸿蒙 (HarmonyOS) / OpenHarmony 原生应用 (Native App) 项目,它利用了 ArkUI 的 XComponent 组件 和 NAPI (Native API) 框架来实现高性能的自定义绘制。
结合您提供的文件结构截图和 CMakeLists.txt 内容,我们可以非常清晰地梳理出这个项目的实现流程、执行环境和核心逻辑。
一、 项目概述与目标
从项目名称 xcomponentvsync 和 Commit 信息 “解决【XComponent + Vsync 实现自定义动画】卡顿和页面不渲染的情况” 来看,这个项目的核心目标是:
使用鸿蒙的 XComponent 组件,结合 Native 层的 EGL/OpenGL ES 进行高性能的自定义图形渲染,并通过 Vsync (垂直同步) 信号来驱动动画的刷新,最终解决UI卡顿问题,实现流畅的自定义动画。
这是一种典型的混合编程模式,UI逻辑和上层控制在 ArkTS (ETS) 中完成,而对性能要求极高的图形渲染任务则下沉到 C++ Native 层处理。
二、 文件结构与职责分析
三、 CMakeLists.txt 解读:构建 Native 动态库
CMakeLists.txt 文件是理解 Native 层实现的关键。
-
project(XCOMPONENTVSYNC): 定义项目名称。
-
add_definitions(-DOHOS_PLATFORM): 定义一个宏,让代码知道当前是为鸿蒙平台编译。
-
include_directories(...): 告诉编译器去哪里查找头文件(.h 文件)。
-
add_library(entry SHARED ...): 核心指令。
- entry: 这是要生成的动态库的名称。最终会生成一个 libentry.so 文件。
- SHARED: 表示生成的是一个共享动态库。
- ...: 列出了所有需要被编译进这个库的 C++ 源文件。
-
find_library(...): 查找系统依赖库。
- 这些指令告诉 CMake 去鸿蒙的 NDK (Native Development Kit) 中查找一系列预置的系统库,如:
- EGL, GLESv3: 图形库,用于OpenGL ES渲染。
- hilog_ndk.z: 鸿蒙的日志库。
- ace_ndk.z, ace_napi.z: ArkUI (ACE) 引擎的原生接口库,NAPI框架的基础。
- native_drawing: 鸿蒙原生的2D绘图库。
- native_vsync: 垂直同步信号库,用于实现流畅动画。
- uv: libuv,一个跨平台的异步I/O库,通常被 Node.js 和 NAPI 用来处理事件循环。
-
target_link_libraries(entry PUBLIC ...): 链接依赖库。
- 这条指令告诉链接器,在生成 libentry.so 时,需要将上面找到的所有系统库都链接进来。这样 libentry.so 才能调用这些系统库提供的函数。
总结: CMakeLists.txt 的作用就是编译 napi_init.cpp 等几个源文件,并把它们和一堆鸿蒙系统提供的原生库(图形、日志、Vsync等)“捆绑”在一起,最终生成一个名为 libentry.so 的核心动态库。
四、 项目实现流程与执行逻辑
整个项目的执行流程跨越了 ArkTS 和 C++ 两层,通过 NAPI 进行通信。
| 环境 |
执行文件/模块 |
逻辑 |
|---|
| ArkUI (ArkTS/JS 运行时) |
pages/Index.ets (假设) |
1. 应用启动,加载 Index.ets 页面。 |
| ArkUI (ArkTS/JS 运行时) |
FeatureComponent.ets |
2. 页面中创建 **** 组件,并为其指定一个 ID 和类型(如 surface 或 texture)。****3. 组件创建成功后,会触发 onLoad 回调函数。 |
| ArkUI (ArkTS/JS 运行时) |
FeatureComponent.ets (在 onLoad 回调中) |
4. 在回调中,通过 requireNapi('entry') 或类似的 import 语句,加载 CMakeLists.txt 生成的 libentry.so **动态库。**5. 通过 NAPI,获取到 libentry.so 中暴露出来的 C++ 对象或函数。 |
| 从 ArkTS 到 C++ (通过 NAPI) |
FeatureComponent.ets -> napi_init.cpp |
6. ArkTS 层调用一个 NAPI 导出的 init(xcomponentContext) 函数,并将 XComponent 的上下文对象 (包含渲染表面 surface 的句柄) 传递给 C++ 层。 |
| Native (C++ 运行时) |
napi_init.cpp -> manager/plugin_manager.cpp |
7. init 函数接收到上下文对象后,开始初始化 Native 层的渲染环境。它会创建一个 PluginManager 实例。 |
| Native (C++ 运行时) |
manager/plugin_manager.cpp -> render/plugin_render.cpp |
8. PluginManager 创建一个 PluginRender 实例,并将从 XComponent 传来的 surface 句柄交给它。 |
| Native (C++ 运行时) |
render/plugin_render.cpp -> EGL/GLES 库 |
9. PluginRender 使用 surface 句柄,通过调用 EGL 库的函数,初始化 OpenGL ES 的渲染环境(创建 EGL Display, Context, Surface)。 |
| Native (C++ 运行时) |
manager/plugin_manager.cpp -> Vsync 库 |
10. PluginManager 向系统的 Vsync 服务注册一个回调函数。这意味着,每当屏幕准备好刷新下一帧时(通常是每秒60次),系统就会调用这个 C++ 回调函数。 |
| Native (C++ 运行时) - 动画循环 |
Vsync 回调 -> render/plugin_render.cpp |
11. (动画循环开始) **Vsync 信号到来,触发回调。**12. 回调函数通知 PluginRender 执行一帧的绘制。 |
| Native (C++ 运行时) - 动画循环 |
render/plugin_render.cpp -> GLES 库 |
13. PluginRender 开始调用 OpenGL ES 的函数(如 glClear, glDrawArrays 等)在 XComponent 的 surface 上进行绘图。动画的每一帧都是在这里被独立渲染的。 |
| Native (C++ 运行时) - 动画循环 |
render/plugin_render.cpp -> EGL 库 |
14. 绘制完成后,调用 eglSwapBuffers(),将后台缓冲区中绘制好的图像提交到前台,显示在屏幕上。 |
| 重复 |
|
15. 等待下一个 Vsync 信号,重复步骤 11-14,形成流畅的动画。 |
简而言之,这个项目的逻辑是:
ArkTS 负责“搭台子”(创建 XComponent 控件),然后把“舞台”(渲染表面)交给 C++。C++ 这位“演员”则完全接管了这个舞台,听从 Vsync 这个“节拍器”的指挥,独立地在上面进行高性能的 OpenGL ES 绘图表演,而不会干扰或被 ArkUI 的主UI线程所干扰,从而实现了流畅的自定义动画。

已经可以正确构建https://gitee.com/harmonyos-cases/cases/tree/master/CommonAppDevelopment/feature/xcomponentvsync
这个项目,但是在preview中不能进行预览?
DevEco Studio 的预览器 (Previewer) 是一个专门用于快速渲染 UI 的、轻量级的、运行在你的电脑 (PC) 上的模拟环境。它只能执行和渲染 ArkUI 框架本身的内容,而不能加载和执行 Native (C/C++) 的动态库 (.so 文件)。
技术原因分析:
-
架构不同:
- 预览器 (PC 端): 运行在你的 Windows/macOS 电脑上,CPU 架构是 x86_64。
- 目标设备 (手机/平板): 运行在鸿蒙系统上,CPU 架构通常是 ARM64 (aarch64)。
- 你的 C++ 代码被交叉编译成了 ARM64 架构的 libentry.so。这个 .so 文件在 x86_64 架构的电脑上是无法直接运行的,就像你不能在 Windows 电脑上直接运行一个安卓 APK 里的 ARM 指令一样。
-
环境隔离与安全:
- 预览器的设计目标是快速、安全地展示 UI 布局和基本交互。它运行在一个沙箱环境中,不会去加载可能包含任意底层代码的 Native 库。
- 加载 .so 库意味着需要一个完整的鸿蒙 Native 运行时环境,包括 NAPI 引擎、EGL/GLES 图形栈、Vsync 服务等。这些在轻量级的预览器中都是不存在的。
-
功能区分:
- 预览 (Previewer): 用于 UI 开发。你可以用它来快速查看你的 Text, Button, Column 等组件的样式、布局和简单的 @State 驱动的UI更新。它看到 XComponent 时,可能只会渲染一个占位符方框。
- 运行 (Run on Emulator/Device): 用于完整功能测试。当你点击 "Run" 按钮时,DevEco Studio 会将完整的 HAP 包(包含了你的 .so 文件)安装到模拟器或真机上。在这个真实的鸿蒙运行环境中,系统加载器会加载你的 HAP,当 ArkTS 代码 import 'libentry.so' 时,系统会找到并加载 libentry.so,你的 C++ 代码才会真正开始执行,NAPI 调用才会生效,OpenGL ES 动画才能被渲染出来。
- 必须使用模拟器 (Emulator) 或真机 (Physical Device) 来运行和测试你的应用。
这里就不真机演练了。