HarmonyOS 6 自定义人脸识别模型1:XComponent入门
在HarmonyOS应用开发中,面对实时画面处理、复杂图形渲染、硬件资源直操作等场景(如人脸识别中的相机预览流解析、AI模型推理结果叠加显示),传统UI组件往往难以满足性能与灵活性需求。而XComponent作为HarmonyOS提供的自定义渲染组件,恰好解决了这一痛点——它支持EGL/OpenGLES图形渲染与媒体数据写入,通过直接操作NativeWindow实现高效绘制,成为复杂场景开发的核心技术支撑。 本系列博客将以“自定义人脸识别模型”为目标,逐步拆解开发流程。第一篇作为入门篇,将聚焦XComponent的核心原理、两种应用场景与实战开发,为后续整合相机流、AI推理模型打下基础。 XComponent是HarmonyOS专为复杂自定义渲染设计的组件,核心作用是提供一个可直接操作的 其核心特性包括: 两种渲染类型: XComponent的生命周期与 两种场景的生命周期时序图: 对于需要在ArkTS侧使用已封装接口进行功能开发(如相机预览、视频播放等)或对跨语言性能损耗不敏感的跨语言开发,建议直接在ArkTS侧使用XComponentController管理Surface生命周期。 对于复杂的交互逻辑需跨语言开发,追求极致渲染性能或业务需求自主控制Surface的创建和销毁的,建议在Native侧使用OH_ArkUI_SurfaceHolder管理Surface生命周期。其生命周期触发时机如下: OnSurfaceCreated回调,触发时刻:当XComponent创建完成且创建好Surface后,满足以下任一条件时触发。 Native侧OnSurfaceCreated的时序如下图: HarmonyOS XComponent 的设计思路与 Android 的 开发范式是标准化的流程模板,XComponent 基于 "创建方式 + 生命周期管理方式" 的组合,提供 5 种开发范式,覆盖不同技术栈需求: XComponent提供两种核心开发场景,分别适用于不同的技术栈需求。以下基于HarmonyOS 6,以“绘制可点击变色的五角星”为例,拆解实战步骤。 核心是通过 XComponent作为HarmonyOS复杂渲染的核心组件,通过 本系列的目标是实现“自定义人脸识别模型”,后续将逐步推进: 通过本系列,你将掌握HarmonyOS中复杂渲染+AI模型整合的完整流程,为开发高性能视觉类应用提供技术支撑。如果在实战中遇到问题,欢迎在评论区交流~一、背景与核心价值
二、XComponent核心原理速览
2.1 什么是XComponent?
surface(绘图表面),开发者通过NativeWindow接口申请、提交绘制缓冲区(Buffer),最终由XComponent将surface整合到应用UI界面中。XComponentType.SURFACE:自定义绘制内容独立显示,适合全屏渲染(如游戏、相机预览);XComponentType.TEXTURE:绘制内容与XComponent组件内容合成显示,适合局部叠加(如人脸识别框、水印)。2.2 自绘制核心流程
图1:XComponent自绘制原理流程图
2.3 生命周期核心事件
surface的创建、销毁强绑定,核心事件包括:onLoad:surface准备就绪时触发,可获取Native层方法上下文,用于初始化渲染环境;onDestroy:组件销毁时触发,需在此释放NativeWindow、EGL上下文等资源,避免内存泄漏。ArkTS XComponent生命周期时序图



Native XComponent生命周期时序图



三、与 Android 自定义渲染组件深度对比
SurfaceView/TextureView相似,但在跨层协作、生命周期管理、灵活性上有显著优化。以下从核心维度对比:对比维度 HarmonyOS XComponent Android SurfaceView Android TextureView 核心渲染载体 Surface(通过 NativeWindow 操作) Surface SurfaceTexture 渲染模式 双模式:SURFACE(独立图层)、TEXTURE(UI 合成) 独立图层(SurfaceFlinger 直接渲染) UI 合成(与 View 树同图层) 创建方式 3 种:ArkTS 声明式、ArkTS 自定义节点、NDK XML 布局 / 代码创建 XML 布局 / 代码创建 生命周期管理 2 种:XComponentController(ArkTS 侧)、OH_ArkUI_SurfaceHolder(Native 侧) SurfaceHolder 回调(surfaceCreated/surfaceDestroyed) SurfaceTextureListener 回调 跨层通信 ArkTS↔Native 通过 Node-API 接口契约,支持直接传递 SurfaceId/NodeHandle Java↔Native 通过 JNI,需手动传递 Surface 对象 需通过 SurfaceTexture 跨层传递,流程繁琐 事件支持 基础事件(触摸 / 键盘 / 鼠标)+ 高级手势(长按 / 拖拽) 仅基础触摸事件,高级手势需自定义 支持 View 树事件传递,但合成有延迟 性能表现 SURFACE 模式无 UI 合成开销,TEXTURE 模式合成效率优化 独立图层无合成开销,性能最优 需 GPU 合成,高帧率场景有性能损耗 灵活性 支持 5 种开发范式,适配不同技术栈 仅支持 Java 层开发,Native 扩展需 JNI 支持 Java 层开发,Native 扩展复杂 资源释放 回调明确,支持自动释放 + 手动释放双重保障 依赖 SurfaceHolder 回调,易遗漏释放导致内存泄漏 需监听 TextureView 销毁,释放逻辑复杂 核心优势总结
SurfaceId/NodeHandle实现 ArkTS 与 Native 的直接通信,无需像 Android 那样通过 JNI 传递复杂对象;四、XComponent 五大开发范式全解析
范式类型 创建方式 生命周期管理方式 核心适用场景 范式 1 ArkTS 声明式 UI XComponentController 通用 UI 开发、相机预览 / 视频播放(ArkTS 为主) 范式 2 ArkTS 声明式 UI OH_ArkUI_SurfaceHolder 复杂交互、跨层性能敏感场景(Native 主导渲染) 范式 3 ArkTS 自定义组件节点 XComponentController 自定义复杂组件、动态布局场景 范式 4 ArkTS 自定义组件节点 OH_ArkUI_SurfaceHolder 复杂组件 + 极致渲染性能需求 范式 5 NDK 接口 OH_ArkUI_SurfaceHolder 纯 Native 开发、底层硬件操作场景 五、XComponent两大应用场景实战
5.1 场景1:Native XComponent(C++主导渲染)
核心特点
libraryname(动态库名称)、id(唯一标识);NativeWindow;开发步骤(关键代码+解释)
步骤1:ArkTS侧定义XComponent
// 声明Native侧接口
export default interface XComponentContext {
drawPattern(): void; // 绘制五角星
getStatus(): { hasDraw: boolean; hasChangeColor: boolean }; // 获取渲染状态
}
@Entry
@Component
struct NativeXComponentDemo {
private xComponentContext: XComponentContext | undefined = undefined;
// 配置XComponent属性:id唯一、类型SURFACE、绑定动态库nativerender
private xComponentAttrs: XComponentAttrs = {
id: 'starRenderId', // 必须唯一
type: XComponentType.SURFACE,
libraryname: 'nativerender' // 与Native层模块名一致
};
build() {
Column() {
XComponent(this.xComponentAttrs)
.focusable(true) // 支持键盘事件
.onLoad((context) => {
// 初始化Native层上下文
this.xComponentContext = context as XComponentContext;
// 调用Native层绘制方法
this.xComponentContext?.drawPattern();
})
.onDestroy(() => {
console.log("XComponent销毁,释放资源");
})
.width('80%')
.height(300);
Button("切换颜色")
.onClick(() => {
const status = this.xComponentContext?.getStatus();
if (status) status.hasChangeColor = true;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}步骤2:Native层Node-API注册
// napi_init.cpp:将C++方法暴露给ArkTS侧
#include <napi/native_api.h>
#include "plugin_manager.h"
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// 暴露getContext接口,用于获取XComponent实例
napi_property_descriptor desc[] = {
{"getContext", nullptr, PluginManager::GetContext, nullptr, nullptr, nullptr, napi_default, nullptr}
};
napi_define_properties(env, exports, sizeof(desc)/sizeof(desc[0]), desc);
// 导出绘制相关方法(drawPattern、getStatus)
PluginManager::GetInstance()->Export(env, exports);
return exports;
}
EXTERN_C_END
// 注册模块,模块名需与ArkTS侧libraryname一致
static napi_module nativerenderModule = {
.nm_version = 1,
.nm_register_func = Init,
.nm_modname = "nativerender", // 关键:与libraryname匹配
.nm_priv = nullptr,
.reserved = {0}
};
// 自动注册模块
extern "C" __attribute__((constructor)) void RegisterModule(void) {
napi_module_register(&nativerenderModule);
}步骤3:事件回调与渲染实现
OH_NativeXComponent_RegisterCallback注册生命周期与触摸/按键事件,利用EGL/GLES绘制图形:// plugin_render.cpp:渲染逻辑实现
void PluginRender::RegisterCallback(OH_NativeXComponent* nativeXComponent) {
// 注册surface创建、改变、销毁回调
renderCallback_.OnSurfaceCreated = OnSurfaceCreatedCB;
renderCallback_.OnSurfaceChanged = OnSurfaceChangedCB;
renderCallback_.OnSurfaceDestroyed = OnSurfaceDestroyedCB;
// 注册触摸事件回调(用于点击变色)
renderCallback_.DispatchTouchEvent = DispatchTouchEventCB;
OH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback_);
}
// surface创建时初始化EGL环境
void OnSurfaceCreatedCB(OH_NativeXComponent* component, void* window) {
std::string id = GetXComponentId(component); // 获取唯一ID
auto render = PluginRender::GetInstance(id);
uint64_t width, height;
OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);
// 初始化EGL上下文,准备绘制
render->eglCore_->EglContextInit(window, width, height);
render->eglCore_->Background(); // 绘制背景
}
// 触摸事件触发颜色切换
void DispatchTouchEventCB(OH_NativeXComponent* component, void* window) {
OH_NativeXComponent_TouchEvent touchEvent;
OH_NativeXComponent_GetTouchEvent(component, window, &touchEvent);
if (touchEvent.type == OH_NATIVEXCOMPONENT_UP) { // 手指抬起时
std::string id = GetXComponentId(component);
auto render = PluginRender::GetInstance(id);
render->eglCore_->ChangeColor(); // 切换五角星颜色
}
}步骤4:CMakeLists配置(编译动态库)
cmake_minimum_required(VERSION 3.4.1)
project(XComponentDemo)
# 头文件目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/include
)
# 编译动态库nativerender
add_library(nativerender SHARED
render/egl_core.cpp
render/plugin_render.cpp
manager/plugin_manager.cpp
napi_init.cpp
)
# 链接依赖库(EGL、GLES、日志等)
target_link_libraries(nativerender PUBLIC
EGL GLESv3 hilog_ndk.z ace_ndk.z ace_napi.z uv
)运行效果


图4:Native XComponent运行效果(左:初始状态;右:点击后变色)
5.2 场景2:ArkTS XComponent(ArkTS主导渲染)
核心特点
libraryname,通过SurfaceId实现跨层通信;SurfaceId并传递给Native层,生命周期与事件回调均在ArkTS侧触发;关键差异点
对比维度 Native XComponent ArkTS XComponent 跨层标识 依赖 id+动态库名依赖 SurfaceId回调触发 Native层注册回调 ArkTS侧通过Controller注册 初始化方式 Native层获取 OH_NativeXComponent实例Native层通过 SurfaceId创建NativeWindow核心代码示例(ArkTS侧)
// 重写XComponentController,监听Surface生命周期
class MyXComponentController extends XComponentController {
// Surface创建时传递SurfaceId到Native层
onSurfaceCreated(surfaceId: string): void {
console.log(`Surface创建:${surfaceId}`);
nativeRender.SetSurfaceId(BigInt(surfaceId)); // 传递给Native
}
// Surface尺寸改变时更新
onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void {
nativeRender.ChangeSurface(BigInt(surfaceId), rect.surfaceWidth, rect.surfaceHeight);
}
// Surface销毁时释放资源
onSurfaceDestroyed(surfaceId: string): void {
nativeRender.DestroySurface(BigInt(surfaceId));
}
}
@Entry
@Component
struct ArkTSXComponentDemo {
private xComponentController = new MyXComponentController();
build() {
Column() {
XComponent({
type: XComponentType.SURFACE,
controller: this.xComponentController
})
.width('80%')
.height(300);
Button("绘制五角星")
.onClick(() => {
const surfaceId = this.xComponentController.getXComponentSurfaceId();
nativeRender.DrawPattern(BigInt(surfaceId)); // 调用Native绘制
});
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}六、注意事项与避坑指南
id(Native场景)或SurfaceId+随机数(ArkTS场景)唯一,否则会导致资源缓存冲突;onDestroy或OnSurfaceDestroyed回调中,需释放NativeWindow、EGL上下文、动态库实例,避免野指针崩溃;typeNode创建XComponent,需先通过OH_NativeWindow_NativeWindowHandleOpt设置缓冲区尺寸,否则绘制失败。七、总结与后续规划
7.1 核心回顾
NativeWindow与EGL/GLES的结合,实现了高效、灵活的自定义绘制能力。本文重点讲解了:7.2 系列博客预告