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

news2024/9/24 11:28:48

介绍

本篇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实例对象的context
  private 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);

    // 初始化display
    m_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()
{
    // 创建surface
    if (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;
    }

    // 创建context
    m_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;
    }

    // 创建program
    m_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实例的id
    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(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侧接口drawRectangle
    napi_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 SHARED
    render/egl_core.cpp
    render/plugin_render.cpp
    manager/plugin_manager.cpp
    napi_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.coloradmin.cn/o/1539652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

搭建Linux内核开发环境——保姆教程(持续更新中)

搭建Linux内核开发环境——保姆教程&#xff08;持续更新中&#xff09; git版本管理汇编器链接器调试器编辑器构建系统模拟器文档工具图形设计工具data manage 在此文中&#xff0c;持续完善&#xff0c;搭建内核开发环境的细节&#xff0c;有需要的小伙伴儿可以持续关注下 g…

[Java、Android面试]_13_map、set和list的区别

本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料。 整理成了面试系列&#xff0c;由于时间有限&#xff0c;每天整理一点&#xff0c;后续会陆续分享出来&#xff0c;感兴趣的朋友可关注收…

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

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

掌握这6大工具,自媒体ai写作之路畅通无阻! #知识分享#媒体#科技

从事自媒体运营光靠自己手动操作效率是非常低的&#xff0c;想要提高运营效率就必须要学会合理的使用一些辅助工具。下面小编就跟大家分享一些自媒体常用的辅助工具&#xff0c;觉得有用的朋友可以收藏分享。 1.元芳写作 这是一个微信公众号 面向专业写作领域的ai写作工具&am…

OFDM调制解调的优势

1、频谱效率高 各子载波可以部分重叠&#xff0c;理论上可以接近Nyquist极限。 实现小区内各用户之间的正交性&#xff0c;避免用户间干扰&#xff0c;取得很高的小区容量。 相对单载波系统&#xff08;WCDMA&#xff09;&#xff0c;多载波技术是更直接实现正交传输的方法 2、…

(AtCoder Beginner Contest 345) ---- F - Many Lamps -- 题解

F - Many Lamps 题目大意&#xff1a; 思路解析&#xff1a; 对于每个线只有三种情况 &#xff08;1&#xff09; 一个城市亮着灯&#xff0c;另一个城市没亮灯&#xff0c;此时选择这条线路&#xff0c;灯的点亮数不变 &#xff08;2&#xff09; 两个城市未亮灯&#xff…

UNI-APP读取本地JSON数据

首先要把json文件放在static文件夹下 然后在要读取数据的页面导入 import data from ../../static/data.json读取数据&#xff1a; onLoad() {console.log(data, data)}, 打印出来的就是JSON文件里的数据了

简介:网络数据中心和数字孪生系统融合

前言 云服务器是在云中提供可扩展的计算服务&#xff0c;避免了使用传统服务器时需要预估资源用量及前期投入的情况。云服务器支持用户自定义一切资源&#xff1a;cpu、内存、硬盘、网络、安全等等&#xff0c;并可在访问量和负载等需求发生变化时轻松地调整它们。云服务器为业…

你需要来自XXX的权限才能对此文件夹进行更改”的解决方法

使用 Windows PowerShell(管理员)(A) 强制删除文件 快捷键 winx 调出如下界面,点击 Windows PowerShell(管理员)(A) 1、定位到要删除的文件夹所在目录&#xff0c;例如E盘下的abc文件夹 执行命令 cd Set-Location -Path "E:\Program Files\abc" 2、给要删除的文件夹…

鸿蒙Harmony应用开发—ArkTS-ForEach:循环渲染

ForEach基于数组类型数据执行循环渲染。 说明&#xff1a; 从API version 9开始&#xff0c;该接口支持在ArkTS卡片中使用。 接口描述 ForEach(arr: Array,itemGenerator: (item: Array, index?: number) > void,keyGenerator?: (item: Array, index?: number): string …

eth 交易案例分析9

交易hash: 先用0.26eth买入了多个GPT&#xff0c;然后用这多个GPT 在uniswap3 兑换了1.69 个eth&#xff0c; 疑问点&#xff1a;买入的 DLP 什么意思&#xff1f;

C++初阶---类和对象

目录 1. 类的引入 2. 类的定义 4. 类的访问限定符及封装 4.1 访问限定符 4.2 封装 5. 类的作用域 6.类的实例化 7.类对象模型 8. this指针 8.1 this指针的引出 8.2 this指针的特性 8.3 C语言和C实现栈的对比 9.类的六个默认成员函数 10&#xff0c;构造函数 10.1…

Penpad 生态资产 $PDD LaunchPad 在即,Season 2 规则解读

Penpad是Scroll上的LauncPad平台&#xff0c;该平台继承了Scroll底层的技术优势&#xff0c;并基于零知识证明技术&#xff0c;推出了系列功能包括账户抽象化、灵活的挖矿功能&#xff0c;并将在未来实现合规为RWA等资产登录Scroll生态构建基础。该平台被认为是绝大多数项目、资…

IM系统设计之消息存储

IM系统设计之消息存储 项目地址&#xff1a;gitgithub.com:muyixiaoxi/Link.git 消息存储结构 消息存储结构如下图所示 当用户A向用户B发送一条消息时 将消息发送给 serverserver 将消息进行持久化判断用户B是否在线&#xff0c;如果在线直接转发&#xff1b;如果离线&am…

蓝桥杯-单片机基础8——上下位机的串口通信设置(附小蜜蜂课程代码)

蓝桥杯单片机组备赛指南请查看这篇文章&#xff1a;戳此跳转蓝桥杯备赛指南文章 本文章针对蓝桥杯-单片机组比赛开发板所写&#xff0c;代码可直接在比赛开发板上使用。 型号&#xff1a;国信天长4T开发板&#xff08;绿板&#xff09;&#xff0c;芯片&#xff1a;IAP15F2K6…

经得住拷问的HTTPS原理解析

此文涵盖的大致内容&#xff1a; 理解HTTPS原理的概念什么是对称加密和非对称加密&#xff1f;什么是数字签名&#xff1f;怎么生成&#xff1f;怎么校验&#xff1f;啥时候是对称加密&#xff1f;啥时候是非对称加密&#xff1f;啥时候进行算法加密&#xff1f;什么算法&…

【SpringCloud】Consul中数据持久化配置并注册为Windows服务

Consul用起来两个比较麻烦的地方: 一是每次都需要执行打开命令行执行 consul agent -dev 命令来启动Consul服务,而且不能关掉命令行.另外一点就是在Consul中设置的数据,每次重启之后就没了. 就很烦.因此为大家带来解决上述问题的方法 首先要在官网下载Consul安装包的解压目录下…

每日一题 --- 设计链表[力扣][Go]

设计链表 题目&#xff1a;707. 设计链表 你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则…

C++生成动态连接库

文章目录 一、静态连接与动态连接二、 动态链接库&#xff08;DLL&#xff09;的创建三、dll库的使用四、 动态链接链接库工作原理五、extern "C" 一、静态连接与动态连接 静态库和动态库区别是库的加载时间不同。静态库&#xff1a;在链接阶段库将会与.o目标文件一起…

【redis】服务器架构演进

架构演进 单机架构应用数据分离架构应⽤服务集群架构读写分离 / 主从分离架构冷热分离架构垂直分库微服务架构 单机架构 所有的应用服务、业务所需的数据、业务处理等都在一台服务器上。 在初期&#xff0c;用户访问量很少&#xff0c;对服务器的的性能和安全没有很高的要求&am…