鸿蒙ArkTS实战开发-Native XComponent组件的使用

介绍

本篇Codelab主要介绍如何使用XComponent组件调用NAPI来创建EGL/GLES环境,实现在主页面绘制一个正方形,并可以改变正方形的颜色。本篇CodeLab使用Native C++模板创建。

如图所示,点击绘制矩形按钮,XComponent组件绘制区域中渲染出一个正方形,点击绘制区域,正方形显示另一种颜色,点击绘制矩形按钮正方形还原至初始绘制的颜色。

相关概念

  • EGL(Embedded Graphic Library):EGL 是Khronos渲染API (如OpenGL ES 或 OpenVG) 与底层原生窗口系统之间的接口。
  • XComponent:可用于EGL/OpenGLES和媒体数据写入,并显示在XComponent组件。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:润和RK3568开发板。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:

2.搭建烧录环境。

    1. 完成DevEco Device Tool的安装
    2. 完成RK3568开发板的烧录

3.搭建开发环境。

    1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用真机进行调测。

代码结构解读

代码目录结构图

本篇Codelab只对核心代码进行讲解。

使用Native C++模板创建项目会自动生成cpp文件夹、types文件夹、CMakeList.txt文件,开发者可以根据实际情况自行添加修改其他文件及文件夹。

├──entry/src/main
│  ├──cpp                           // C++代码区
│  │  ├──CMakeLists.txt             // CMake配置文件
│  │  ├──napi_init.cpp              // C++源代码
│  │  ├──common
│  │  │  └──common.h                // 常量定义文件
│  │  ├──manager                    // 生命周期管理模块
│  │  │  ├──plugin_manager.cpp
│  │  │  └──plugin_manager.h
│  │  ├──render                     // 渲染模块
│  │  │  ├──egl_core.cpp
│  │  │  ├──egl_core.h
│  │  │  ├──plugin_render.cpp
│  │  │  └──plugin_render.h
│  │  └──types                      // 接口存放文件夹
│  │     └──libhello
│  │        ├──index.d.ts           // 接口文件
│  │        └──oh-package.json5     // 接口注册配置文件
│  └──ets                           // 代码区
│     ├──common
│     │  └──CommonConstants.ets     // 常量定义文件
│     ├──entryability
│     │  └──EntryAbility.ts         // 程序入口类
│     └──pages                      // 页面文件
│        └──Index.ets               // 主界面
└──entry/src/main/resources         // 资源文件目录

应用架构

应用架构如图所示。其中,C++侧代码用于实现业务逻辑,ArkTS侧代码用于展示前端界面以及调用相关方法。

  • C++侧:实现图形渲染方法并进行接口函数封装,管理应用和页面的生命周期。
  • ArkTS侧:实现前端界面,调用C++侧的图形渲染方法,并在应用和页面的生命周期调用相关C++侧方法。
  • CMake编译工具链:跨平台的编译工具,将C++侧代码编译成so文件提供给ArkTS侧。

界面设计

主界面由标题、绘制区域、按钮组成。Index.ets文件完成界面实现,使用Column及Row容器组件进行布局。

// Index.ets
@Entry
@Component
struct Index {...build() {Column() {Row() {...}.height($r('app.float.title_height'))Column() {XComponent({...})}.height(CommonConstants.XCOMPONENT_HEIGHT)Row() {Button($r('app.string.button_text')).fontSize($r('app.float.button_font_size')).fontWeight(CommonConstants.FONT_WEIGHT)}.width(CommonConstants.FULL_PARENT)}.width(CommonConstants.FULL_PARENT).height(CommonConstants.FULL_PARENT)}
}

ArkTS侧方法调用

ArkTS侧方法调用步骤如下:

  • 使用import语句导入编译生成的动态链接库文件。
  • 增加XComponent组件,设置XComponent组件的唯一标识id,指定XComponent组件类型及需要链接的动态库名称。
  • 组件链接动态库加载完成后回调onLoad()方法,指定XComponent组件的上下文环境,上下文环境包含来自C++挂载的方法。
  • 新增Button组件,绑定由NAPI注册的drawRectangle()方法,实现绘制正方形的功能。
// Index.ets
// 导入动态链接库
import nativerender from 'libnativerender.so';@Entry
@Component
struct Index {// XComponent实例对象的contextprivate xComponentContext: Record<string, () => void> = {};...build() {...// 增加XComponent组件XComponent({id: CommonConstants.XCOMPONENT_ID,type: CommonConstants.XCOMPONENT_TYPE,libraryname: CommonConstants.XCOMPONENT_LIBRARY_NAME}).onLoad((xComponentContext?: object | Record<string, () => void>) => {if (xComponentContext) {this.xComponentContext = xComponentContext as Record<string, () => void>;}})...// 增加Button组件Button($r('app.string.button_text')).onClick(() => {if (this.xComponentContext) {this.xComponentContext.drawRectangle();}})...}
}

C++侧功能实现

渲染功能实现

  • 进行环境的初始化,包括初始化可用的EGLDisplay、确定可用的surface配置、创建渲染区域surface、创建并关联上下文等。
// egl_core.cpp
bool EGLCore::EglContextInit(void *window, int width, int height)
{OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "EglContextInit execute");if ((nullptr == window) || (0 >= width) || (0 >= height)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "EglContextInit: param error");return false;}m_width = width;m_height = height;if (0 < m_width) {// 计算绘制矩形宽度百分比m_widthPercent = FIFTY_PERCENT * m_height / m_width;}m_eglWindow = static_cast<EGLNativeWindowType>(window);// 初始化displaym_eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);if (EGL_NO_DISPLAY == m_eglDisplay) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglGetDisplay: unable to get EGL display");return false;}EGLint majorVersion;EGLint minorVersion;if (!eglInitialize(m_eglDisplay, &majorVersion, &minorVersion)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore","eglInitialize: unable to get initialize EGL display");return false;}// 选择配置const EGLint maxConfigSize = 1;EGLint numConfigs;if (!eglChooseConfig(m_eglDisplay, ATTRIB_LIST, &m_eglConfig, maxConfigSize, &numConfigs)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglChooseConfig: unable to choose configs");return false;}// 创建环境return CreateEnvironment();
}bool EGLCore::CreateEnvironment()
{// 创建surfaceif (nullptr == m_eglWindow) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "m_eglWindow is null");return false;}m_eglSurface = eglCreateWindowSurface(m_eglDisplay, m_eglConfig, m_eglWindow, NULL);if (nullptr == m_eglSurface) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore","eglCreateWindowSurface: unable to create surface");return false;}// 创建contextm_eglContext = eglCreateContext(m_eglDisplay, m_eglConfig, EGL_NO_CONTEXT, CONTEXT_ATTRIBS);if (!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglMakeCurrent failed");return false;}// 创建programm_program = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER);if (PROGRAM_ERROR == m_program) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "CreateProgram: unable to create program");return false;}return true;
}
  • 使用EGL渲染正方形。实现一个渲染函数,通过调用EGL相关API进行绘制。绘制颜色使用小数表示,需要将十六进制颜色转换成十进制数值除以255计算。例如#182431转换十进制分别为24/36/49。
// common.h
/*** 绘制矩形颜色 #7E8FFB*/
const GLfloat DRAW_COLOR[] = { 126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f };/*** 绘制背景颜色 #182431*/
const GLfloat BACKGROUND_COLOR[] = { 24.0f / 255, 36.0f / 255, 49.0f / 255, 1.0f };/*** 绘制背景顶点*/
const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = {-1.0f, 1.0f,1.0f, 1.0f,1.0f, -1.0f,-1.0f, -1.0f
};// egl_core.cpp
void EGLCore::Draw()
{m_flag = false;OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "Draw");// 绘制准备工作GLint position = PrepareDraw();if (POSITION_ERROR == position) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw get position failed");return;}// 绘制背景if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES,sizeof(BACKGROUND_RECTANGLE_VERTICES))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw background failed");return;}// 确定绘制矩形的顶点,使用绘制区域的百分比表示const GLfloat rectangleVertices[] = {-m_widthPercent, FIFTY_PERCENT,m_widthPercent, FIFTY_PERCENT,m_widthPercent, -FIFTY_PERCENT,-m_widthPercent, -FIFTY_PERCENT};// 绘制矩形if (!ExecuteDraw(position, DRAW_COLOR, rectangleVertices, sizeof(rectangleVertices))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw rectangle failed");return;}// 绘制后操作if (!FinishDraw()) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw FinishDraw failed");return;}// 标记已绘制m_flag = true;
}// 绘制前准备,获取position,创建成功时position值从0开始
GLint EGLCore::PrepareDraw()
{if ((nullptr == m_eglDisplay) || (nullptr == m_eglSurface) || (nullptr == m_eglContext) ||(!eglMakeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "PrepareDraw: param error");return POSITION_ERROR;}// 下列方法无返回值glViewport(DEFAULT_X_POSITION, DEFAULT_X_POSITION, m_width, m_height);glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT);glClear(GL_COLOR_BUFFER_BIT);glUseProgram(m_program);return glGetAttribLocation(m_program, POSITION_NAME);
}// 依据传入参数在指定区域绘制指定颜色
bool EGLCore::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat rectangleVertices[],unsigned long vertSize)
{if ((0 > position) || (nullptr == color) || (RECTANGLE_VERTICES_SIZE != vertSize / sizeof(rectangleVertices[0]))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ExecuteDraw: param error");return false;}// 下列方法无返回值glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, rectangleVertices);glEnableVertexAttribArray(position);glVertexAttrib4fv(1, color);glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE);glDisableVertexAttribArray(position);return true;
}// 结束绘制操作
bool EGLCore::FinishDraw()
{// 强制刷新缓冲glFlush();glFinish();// 交换前后缓存return eglSwapBuffers(m_eglDisplay, m_eglSurface);
}
  • 改变正方形的颜色。重新绘制一个大小相同、颜色不同的正方形,与原正方形地址交换,实现改变颜色的功能。
// common.h
/*** 改变后绘制的颜色 #92D6CC*/
const GLfloat CHANGE_COLOR[] = { 146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f };// egl_core.cpp
void EGLCore::ChangeColor()
{// 界面未绘制矩形时退出if (!m_flag) {return;}// 绘制准备工作GLint position = PrepareDraw();if (POSITION_ERROR == position) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor get position failed");return;}// 绘制背景if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES,sizeof(BACKGROUND_RECTANGLE_VERTICES))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor execute draw background failed");return;}// 确定绘制矩形的顶点,使用绘制区域的百分比表示const GLfloat rectangleVertices[] = {-m_widthPercent, FIFTY_PERCENT,m_widthPercent, FIFTY_PERCENT,m_widthPercent, -FIFTY_PERCENT,-m_widthPercent, -FIFTY_PERCENT};// 使用新的颜色绘制矩形if (!ExecuteDraw(position, CHANGE_COLOR, rectangleVertices, sizeof(rectangleVertices))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor execute draw rectangle failed");return;}// 结束绘制if (!FinishDraw()) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor FinishDraw failed");}
}

使用NAPI将C++方法传递给ArkTS

  • 创建接口函数NapiDrawRectangle(),封装对应C++渲染方法。根据XComponent组件信息,获取对应的渲染模块render,调用绘制矩形的方法。

注册为ArkTS侧接口的方法,固定为napi_value (*napi_callback)(napi_env env, napi_callback_info info)类型,不可更改。napi_value为NAPI定义的指针,无返回值时返回 nullptr。

// plugin_render.cpp
// NAPI注册方法固定参数及返回值类型,无返回值时返回nullptr
napi_value PluginRender::NapiDrawRectangle(napi_env env, napi_callback_info info)
{OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "NapiDrawRectangle");if ((nullptr == env) || (nullptr == info)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "NapiDrawRectangle: env or info is null");return nullptr;}// 获取环境变量参数napi_value thisArg;if (napi_ok != napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "NapiDrawRectangle: napi_get_cb_info fail");return nullptr;}// 获取环境变量中XComponent实例napi_value exportInstance;if (napi_ok != napi_get_named_property(env, thisArg, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender","NapiDrawRectangle: napi_get_named_property fail");return nullptr;}OH_NativeXComponent *nativeXComponent = nullptr;if (napi_ok != napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "NapiDrawRectangle: napi_unwrap fail");return nullptr;}// 获取XComponent实例的idchar idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NATIVEXCOMPONENT_RESULT_SUCCESS != OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender","NapiDrawRectangle: Unable to get XComponent id");return nullptr;}std::string id(idStr);PluginRender *render = PluginRender::GetInstance(id);if (render) {// 调用绘制矩形的方法render->m_eglCore->Draw();OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "render->m_eglCore->Draw() executed");}return nullptr;
}
  • 使用NAPI中的napi_define_properties方法,将接口函数NapiDrawRectangle()注册为ArkTS侧接口drawRectangle(),在ArkTS侧调用drawRectangle()方法,完成正方形的绘制。
// plugin_render.cpp
void PluginRender::Export(napi_env env, napi_value exports)
{if ((nullptr == env) || (nullptr == exports)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "Export: env or exports is null");return;}// 将接口函数注册为ArkTS侧接口drawRectanglenapi_property_descriptor desc[] = {{ "drawRectangle", nullptr, PluginRender::NapiDrawRectangle, nullptr, nullptr, nullptr, napi_default, nullptr }};if (napi_ok != napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "Export: napi_define_properties failed");}
}

使用NAPI实现触摸事件回调函数

通过将C++方法封装至触摸事件回调函数的方式,实现触摸绘制区域时改变正方形的颜色的功能。

  • 创建一个新函数DispatchTouchEventCB(),将改变颜色的C++方法ChangeColor()封装于其中。
  • 将新函数绑定为组件触摸事件的回调函数,组件绘制区域产生触摸事件时触发,改变正方形展示的颜色。
// plugin_render.cpp
void DispatchTouchEventCB(OH_NativeXComponent *component, void *window)
{OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchTouchEventCB");if ((nullptr == component) || (nullptr == window)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback","DispatchTouchEventCB: component or window is null");return;}char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NATIVEXCOMPONENT_RESULT_SUCCESS != OH_NativeXComponent_GetXComponentId(component, idStr, &idSize)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback","DispatchTouchEventCB: Unable to get XComponent id");return;}std::string id(idStr);PluginRender *render = PluginRender::GetInstance(id);if (nullptr != render) {// 封装改变颜色的函数render->m_eglCore->ChangeColor();}
}PluginRender::PluginRender(std::string &id)
{this->m_id = id;this->m_eglCore = new EGLCore();auto renderCallback = &PluginRender::m_callback;renderCallback->OnSurfaceCreated = OnSurfaceCreatedCB;renderCallback->OnSurfaceChanged = OnSurfaceChangedCB;renderCallback->OnSurfaceDestroyed = OnSurfaceDestroyedCB;// 设置触摸事件的回调函数,在触摸事件触发时调用NAPI接口函数,从而调用原C++方法renderCallback->DispatchTouchEvent = DispatchTouchEventCB;
}

释放相关资源

释放申请资源的步骤如下:

  • EGLCore类下创建Release()方法,释放初始化环境时申请的资源,包含窗口display、渲染区域surface、环境上下文context等。
  • PluginRender类添加Release()方法,释放EGLCore实例及PluginRender实例。
  • 创建一个新方法OnSurfaceDestroyedCB(),将PluginRender类内释放资源的方法Release()封装于其中。
  • 将新方法绑定为组件销毁事件的回调方法,组件销毁时触发,释放相关资源。
// egl_core.cpp
void EGLCore::Release()
{if ((nullptr == m_eglDisplay) || (nullptr == m_eglSurface) || (!eglDestroySurface(m_eglDisplay, m_eglSurface))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroySurface failed");}if ((nullptr == m_eglDisplay) || (nullptr == m_eglContext) || (!eglDestroyContext(m_eglDisplay, m_eglContext))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroyContext failed");}if ((nullptr == m_eglDisplay) || (!eglTerminate(m_eglDisplay))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglTerminate failed");}
}// plugin_render.cpp
void PluginRender::Release(std::string &id)
{PluginRender *render = PluginRender::GetInstance(id);if (nullptr != render) {render->m_eglCore->Release();delete render->m_eglCore;render->m_eglCore = nullptr;delete render;render = nullptr;m_instance.erase(m_instance.find(id));}
}void OnSurfaceDestroyedCB(OH_NativeXComponent *component, void *window)
{OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnSurfaceDestroyedCB");if ((nullptr == component) || (nullptr == window)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback","OnSurfaceDestroyedCB: component or window is null");return;}char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NATIVEXCOMPONENT_RESULT_SUCCESS != OH_NativeXComponent_GetXComponentId(component, idStr, &idSize)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback","OnSurfaceDestroyedCB: Unable to get XComponent id");return;}// 释放申请的资源std::string id(idStr);PluginRender::Release(id);
}PluginRender::PluginRender(std::string &id)
{this->m_id = id;this->m_eglCore = new EGLCore();OH_NativeXComponent_Callback *renderCallback = &PluginRender::m_callback;renderCallback->OnSurfaceCreated = OnSurfaceCreatedCB;renderCallback->OnSurfaceChanged = OnSurfaceChangedCB;// 设置组件销毁事件的回调函数,组件销毁时触发相关操作,释放申请的资源renderCallback->OnSurfaceDestroyed = OnSurfaceDestroyedCB;renderCallback->DispatchTouchEvent = DispatchTouchEventCB;
}

注册与编译

在napi_init.cpp文件中,Init方法注册上文实现的接口函数,从而将封装的C++方法传递出来,供ArkTS侧调用。编写接口的描述信息,根据实际需要可以修改对应参数。__attribute__((constructor))修饰的方法由系统自动调用,使用NAPI接口napi_module_register()传入模块描述信息进行模块注册。Native C++模板创建项目会自动生成此结构代码,开发者可根据实际情况修改其中内容。

// napi_init.cpp
static napi_value Init(napi_env env, napi_value exports)
{OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Init", "Init begins");if ((nullptr == env) || (nullptr == exports)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "env or exports is null");return nullptr;}napi_property_descriptor desc[] = {{ "getContext", nullptr, PluginManager::GetContext, nullptr, nullptr, nullptr, napi_default, nullptr }};// 将接口函数注册为ArkTS侧接口getContext()if (napi_ok != napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed");return nullptr;}// 方法内检查环境变量是否包含XComponent组件实例,若实例存在注册绘制相关接口PluginManager::GetInstance()->Export(env, exports);return exports;
}static napi_module nativerenderModule = {.nm_version = 1,.nm_flags = 0,.nm_filename = nullptr,.nm_register_func = Init,        // 入口函数.nm_modname = "nativerender",    // 模块名称.nm_priv = ((void *)0),.reserved = { 0 }
};extern "C" __attribute__((constructor)) void RegisterModule(void)
{napi_module_register(&nativerenderModule);
}

使用CMake工具链将C++源代码编译成动态链接库文件。本篇Codelab中会链接两次动态库,第一次为import语句,第二次为XComponent组件链接动态库。

# CMakeList.txt
# 声明使用 CMAKE 的最小版本号
cmake_minimum_required(VERSION 3.4.1)# 配置项目信息
project(XComponent)# set命令,格式为set(key value),表示设置key的值为value
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})# 设置头文件的搜索目录
include_directories(${NATIVERENDER_ROOT_PATH}${NATIVERENDER_ROOT_PATH}/include
)# 添加名为nativerender的库,库文件名为libnativerender.so,添加cpp文件
add_library(nativerender SHAREDrender/egl_core.cpprender/plugin_render.cppmanager/plugin_manager.cppnapi_init.cpp
)
...
# 添加构建需要链接的库
target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libc++.a)

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

  1. 使用XComponent组件调用NAPI创建EGL/GLES环境。
  2. 使用OpenGL ES进行开发渲染。
  3. 使用回调函数响应触摸事件。

为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→HarmonyOS教学视频

HarmonyOS教学视频

鸿蒙语法ArkTS、TypeScript、ArkUI等…视频教程

鸿蒙生态应用开发白皮书V2.0PDF:

获取完整版白皮书方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF

在这里插入图片描述

鸿蒙 (Harmony OS)开发学习手册

一、入门必看

  1. 应用开发导读(ArkTS)
  2. .……

在这里插入图片描述


二、HarmonyOS 概念

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全

在这里插入图片描述

三、如何快速入门?《鸿蒙基础入门学习指南》

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. .……

在这里插入图片描述


四、开发基础知识

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. .……

在这里插入图片描述


五、基于ArkTS 开发

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 7.网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. .……

在这里插入图片描述


更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/283743.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

递归和递推的区别

目录 1、递推 2、递归 3、结言 递归 递推 1、递推 递推就是说从初值出发后一直运算到所需的结果。 ——从已知到未知。&#xff08;从小到大&#xff09; 举一个简单的例子&#xff1a; 每天能学习一个小时的编程&#xff0c;那么一个月之后可以学到三十小时的编程知识。…

同义词的作用

oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 同义词 同义词本质上属于近义词的概念&#xff0c;它是表、索引、视图等模式对象的一个别名 通过为模式对象创建同义词&#xff0c;可以隐藏对象的实际名称和所有者信息&a…

Linux安装Nacos

安装前必要准备 准备Java环境 &#xff0c;8以上的版本&#xff0c;mysql&#xff08;集群相关信息&#xff09;&#xff0c;nginx&#xff08;进行代理&#xff09; 安装Nacos 首先我们要有一个nacos的包&#xff0c;我们可以在线下载&#xff0c;也可以提前下载好&#xf…

【爬取网易财经文章】

引言 在信息爆炸的时代&#xff0c;获取实时的财经资讯对于投资者和金融从业者来说至关重要。然而&#xff0c;手动浏览网页收集财经文章耗时费力&#xff0c;为了解决这一问题&#xff0c;本文将介绍如何使用Python编写一个爬虫程序来自动爬取网易财经下关于财经的文章 1. 爬…

【史上最全面arduino esp32教程】I2C接口LCD1602的使用

文章目录 前言一、安装驱动库二、LCD1602的各种操作2.1 点亮LCD16022.2 LCD1602其他函数清除显示屏上的所有字符将光标位置移动到显示屏的起始位置关闭显示屏&#xff0c;不会显示任何字符打开显示屏&#xff0c;开始显示字符关闭光标闪烁打开光标闪烁&#xff0c;使光标呈现闪…

踩了一天Prophet的fbprophet坑

pip怎么安装Prophet 安装了这个不行,要安装fbprophet 然后安装不起 哦豁 anaconda虚拟环境安装好将其导入pycharm from fbprophet import Prophet 然后不报错了,很稀奇对吧,不报错了 但是运行还是给你显示 没有fbprophet 绝望了,找人吧 通过官方网站安装最新版Prophet,但是…

洛谷_P1873 [COCI 2011/2012 #5] EKO / 砍树_python写法

P1873 [COCI 2011/2012 #5] EKO / 砍树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) n, m map(int,input().split())data list(map(int,input().split())) h 0 def check(mid):h 0for i in data:if i>mid:h (i-mid)if h < m:return Trueelse:return Falsel 0 r …

游戏反云手机检测方案

游戏风险环境&#xff0c;是指独立于原有设备或破坏设备原有系统的环境。常见的游戏风险环境有&#xff1a;云手机、虚拟机、虚拟框架、iOS越狱、安卓设备root等。 这类风险环境可以为游戏外挂、破解提供所需的高级别设备权限&#xff0c;当游戏处于这些风险环境下&#xff0c…

【保姆级】前端使用node.js基础教程

文章目录 安装和版本管理&#xff1a;npm 命令&#xff08;Node 包管理器&#xff09;&#xff1a;运行 Node.js 脚本&#xff1a;调试和开发工具&#xff1a;其他常用命令&#xff1a;模块管理&#xff1a;包管理&#xff1a;调试工具&#xff1a;异步编程和包管理&#xff1a…

python的O2O生鲜食品订购flask-django-nodejs-php

用户只能通过一些类似软件进行查看生鲜超市&#xff0c;这样的管理方式仍然是比较机械传统的&#xff0c;本文通过对市面上常见的线上管理系统与现实生活中结合问题的讨论&#xff0c;从一个微信小程序的O2O生鲜食品订购角度进行需求分析&#xff0c;提供一些新的思路&#xff…

使用Lerna搭建业务组件库

Lerna基本概念 Lerna 是一个用来优化托管在 git\npm 上的多 package 代码库的工作流的一个管理工具,可以让你在主项目下管理多个子项目&#xff0c;从而解决了多个包互相依赖&#xff0c;且发布时需要手动维护多个包的问题。 主要功能&#xff1a; 为单个包或多个包运行命令 …

基于Gabor滤波器的指纹图像识别,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

vben admin路由跳转拿不到param参数问题

vben admin路由跳转拿不到param参数问题 问题原因&#xff1a; 也就是说&#xff0c;从Vue Router的2022-8-22 这次更新后&#xff0c;我们使用上面的方式在新页面无法获取&#xff1a; vue也给我们提出了解决方案&#xff1a; ​ 1.使用 query 的方式传参 ​ 2.将参数放…

阿里云服务器租用一年多少钱?2024年最新阿里云租用价格

2024年阿里云服务器租用费用&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核4G服务…

C# 对App.config、Web.config的appSettings节点数据进行加密

appSettings加密原因&#xff0c;就是因为容易暴露服务器账号和密码&#xff0c;而且客户也不允许 使用ASP.NET提供的命令工具aspnet_regiis来创建加密命令&#xff1b;aspnet_regiis是提供了直接对配置文件加密的功能的&#xff1b;并且使用aspnet_regiis加密的配置节点在读取…

Warning logs 2024-03-23

给旧的笔记本安装ubuntu系统&#xff0c;并实现ssh远程连接 1、下载ubuntu系统 ubuntu下载链接 选择带桌面版本 2、准备U盘 3、使用UltraISO制作启动盘 破解UltraISO软件 输入 注册名&#xff1a;王涛 注册码&#xff1a;7C81-1689-4046-626F 使用UltraISO&#xff0c…

【python_往企业微信群中发送文件】

python_往企业微信群中发送文件 这个是用企业微信群机器人的功能&#xff0c;没有用到后台应用。群机器人 #-*- coding:utf-8-* import requests#类型&#xff1a;voice,file file_type"file" file_path"D:\desktop\不过.jpg" webhookkey"xxxx"#…

vue项目:使用xlsx导出Excel数据

文章目录 一、安装xlsx二、报错及解决三、编写公共方法四、方法使用 一、安装xlsx 执行命令&#xff1a;npm i xlsx file-saver --save 二、报错及解决 使用时&#xff1a;import XLSX from "xlsx"; 发现如下报错信息 报错原因&#xff1a;xlsx版本不兼容。 解…

RabbitMQ部署指南

RabbitMQ部署指南 RabbitMQ部署指南1.单机部署1.1.下载镜像1.2.安装MQ 2.安装DelayExchange插件2.1.下载插件2.2.上传插件2.3.安装插件 3.集群部署2.1.集群分类2.2.获取cookie2.3.准备集群配置2.4.启动集群2.5.测试 4.镜像模式4.1.镜像模式的特征4.2.镜像模式的配置4.3.测试 5.…

Modbus TCP转Profinet网关如何实现Modbus主站与多设备通讯

在工业控制领域中&#xff0c;Modbus TCP转Profinet网关&#xff08;XD-ETHPN20&#xff09;扮演着连接不同设备间通讯的重要角色。当将Modbus主站与十几台服务器进行通讯时&#xff0c;通过modbus tcp转profinet网关&#xff08;XD-ETHPN20&#xff09;设备将不同协议间的数据…