鸿蒙(API 12 Beta6版)图形【AR物体摆放】 AR引擎服务

news2024/9/20 20:35:57

概要

本章节通过AR Engine识别设备周围的平面,并允许用户在平面上放置虚拟物体,实现虚拟和现实的融合。AR物体摆放可用于虚拟家具、数字展厅等应用,给用户提供虚实结合的新体验。通过本示例,您可以学习并掌握如何使用AR Engine开发一款AR应用。

图1 AR物体摆放示意图
1

本章节涉及的AR Engine能力如下:

  • 运动跟踪能力
  • 环境跟踪能力(平面检测)
  • 命中检测能力

业务流程

2

  1. 用户打开应用。
  2. 应用需要向用户申请相机权限,用户同意后继续后续步骤。
  3. 用户点击ArWorld。
  4. AR Engine初始化。
  5. 调用[HMS_AREngine_ARSession_Create]函数创建并返回[AREngine_ARSession]会话;同时调用[HMS_AREngine_ARFrame_Create]函数创建并返回[AREngine_ARFrame]对象。
  6. UI设置定时器,定时触发帧绘制。
  7. 调用[HMS_AREngine_ARSession_Update]函数更新当前帧的ARFrame对象,并返回该对象。
  8. 获取平面。首先,调用[HMS_AREngine_ARSession_GetAllTrackables]函数获取平面类型(ARENGINE_TRACKABLE_PLANE)的可跟踪对象列表。其次,调用[HMS_AREngine_ARTrackableList_AcquireItem]函数从可跟踪对象列表中获指定索引的平面。
  9. 绘制平面。
  10. 将绘制的平面显示在预览画面上。
  11. 用户点击屏幕。
  12. 获取屏幕点击坐标。
  13. 进行碰撞检测。调用[HMS_AREngine_ARFrame_HitTest]函数获取并返回碰撞检测结果。
  14. 根据碰撞检测结果,调用[HMS_AREngine_ARHitResult_AcquireNewAnchor]函数创建锚点,并返回锚点对象。
  15. 在锚点位置绘制虚拟物体。
  16. 将虚拟物体显示在预览画面上。

接口说明

以下接口为AR物体摆放相关接口。

接口名描述
HMS_AREngine_ARSession_Create创建一个新的AREngine_ARSession会话。
HMS_AREngine_ARSession_Update更新AREngine的计算结果。
HMS_AREngine_ARFrame_Create创建一个新的AREngine_ARFrame对象,将指针存储到中*outFrame。
HMS_AREngine_ARSession_SetDisplayGeometry设置显示的高和宽(以像素为单位)。该高和宽是显示view的高和宽,如果不一致,会导致显示相机预览出错。
HMS_AREngine_ARSession_SetCameraGLTexture设置可用于存储相机预览流数据的openGL纹理。
HMS_AREngine_ARSession_GetAllTrackables获取所有指定类型的可跟踪对像集合。
HMS_AREngine_ARTrackableList_AcquireItem从可跟踪列表中获取指定index的对象。
HMS_AREngine_ARPlane_GetCenterPose获取从平面的局部坐标系到世界坐标系转换的位姿信息。
HMS_AREngine_ARHitResultList_Create创建一个命中检测结果对象列表。
HMS_AREngine_ARFrame_HitTest根据屏幕上兴趣点位置获取命中检测结果。
HMS_AREngine_ARHitResultList_GetSize获取命中检测结果对象列表中包含的对象数。
HMS_AREngine_ARHitResultList_GetItem在命中检测结果列表中获取指定索引的命中检测结果对象。
HMS_AREngine_ARHitResult_Create创建一个空的命中检测结果对象。
HMS_AREngine_ARHitResult_AcquireNewAnchor在碰撞命中位置创建一个新的锚点。
HMS_AREngine_ARHitResult_AcquireTrackable获取被命中的可追踪对象。
HMS_AREngine_ARFrame_AcquireCamera获取当前帧的相机参数对象。
HMS_AREngine_ARPose_Create分配并初始化一个新的位姿对象。
HMS_AREngine_ARCamera_GetPose获取当前相机对象在AR世界空间中的位姿。

开发步骤

本章节给出了关键开发步骤,

创建Native C++工程

使用DevEco Studio创建一个Native C++工程。

3

4

申请权限

AR Engine需要使用相机、加速度传感器以及陀螺仪传感器权限,开发者可参考[声明权限]中的方式进行声明。其中相机权限需要用户手动进行授权。

权限名说明授权方式
ohos.permission.CAMERA允许使用相机user_grant
ohos.permission.ACCELEROMETER允许使用加速度传感器system_grant
ohos.permission.GYROSCOPE允许使用陀螺仪传感器system_grant

声明Native接口

ArkTs接口声明。

// 此代码来源于示例代码:ArSample/entry/src/main/cpp/types/libentry/index.d.ts。
import resourceManager from '@ohos.resourceManager';
export const start:(id:string)=>void;
export const show:(id:string)=>void;
export const hide:(id:string)=>void;
export const update:(id:string)=>number;
export const stop:(id:string)=>void;
export const init:(resmgr : resourceManager.ResourceManager)=>void;

建立ArkTs接口与C++接口之间的映射。

// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/module.cpp。
napi_property_descriptor desc[] = {
    { "init", nullptr, Global::Init, nullptr, nullptr, nullptr, napi_default, nullptr },
    { "start", nullptr, NapiManager::NapiOnPageAppear, nullptr, nullptr, nullptr, napi_default, nullptr },
    { "show", nullptr, NapiManager::NapiOnPageShow, nullptr, nullptr, nullptr, napi_default, nullptr },
    { "hide", nullptr, NapiManager::NapiOnPageHide, nullptr, nullptr, nullptr, napi_default, nullptr },
    { "update", nullptr, NapiManager::NapiOnPageUpdate, nullptr, nullptr, nullptr, napi_default, nullptr },
    { "stop", nullptr, NapiManager::NapiOnPageDisappear, nullptr, nullptr, nullptr, napi_default, nullptr }
};

创建UI界面

创建一个UI界面,用于显示相机预览画面,并定时触发每一帧绘制。

// 此代码来源于示例代码:ArSample/entry/src/main/ets/pages/ArWorld.ets。
import { Logger } from '../utils/Logger';
import arEngineDemo from 'libentry.so';
import { resourceManager } from '@kit.LocalizationKit';
import { display } from '@kit.ArkUI';

@Entry
@Component
struct ArWorld {
  private xcomponentId = 'ArWorld';
  private resMgr: resourceManager.ResourceManager = getContext(this).resourceManager;
  private interval: number = -1;
  private isUpdate: boolean = true;

  aboutToAppear() {
    Logger.debug('aboutToAppear ' + this.xcomponentId);
    arEngineDemo.init(this.resMgr);
    arEngineDemo.start(this.xcomponentId);
    display.on("foldStatusChange", (foldStatus: display.FoldStatus) => {
      Logger.info('foldStatusChange display on ' + foldStatus);
      if (foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED
        || foldStatus === display.FoldStatus.FOLD_STATUS_FOLDED) {
        arEngineDemo.stop(this.xcomponentId);
        arEngineDemo.init(this.resMgr);
        // 调用Native的start接口,创建ARSession。
        arEngineDemo.start(this.xcomponentId);
        arEngineDemo.show(this.xcomponentId);
      }
    })
  }

  aboutToDisappear() {
    Logger.debug('aboutToDisappear ' + this.xcomponentId);
    arEngineDemo.stop(this.xcomponentId);
  }

  onPageShow() {
    this.isUpdate = true;
    Logger.debug('onPageShow ' + this.xcomponentId);
    arEngineDemo.show(this.xcomponentId);
  }

  onPageHide() {
    Logger.debug('onPageHide ' + this.xcomponentId);
    this.isUpdate = false;
    arEngineDemo.hide(this.xcomponentId);
  }

  build() {
    Column() {
      XComponent({ id: this.xcomponentId, type: XComponentType.SURFACE, libraryname: 'entry' })
        .onLoad(() => {
          Logger.debug('XComponent onLoad ' + this.xcomponentId);
          this.interval = setInterval(() => {
            if (this.isUpdate) {
              // 调用Native的update,更新AR Engine每一帧的计算结果
              arEngineDemo.update(this.xcomponentId);
            }
          }, 33); // 控制帧率为30fps(每33毫秒刷新一帧)。
        })
        .width('100%')
        .height('100%')
        .onDestroy(() => {
          Logger.debug('XComponent onDestroy ' + this.xcomponentId);
          clearInterval(this.interval);
        })
        .backgroundColor(Color.White)
    }
    .justifyContent(FlexAlign.SpaceAround)
    .alignItems(HorizontalAlign.Center)
    .backgroundColor(Color.White)
    .borderRadius(24)
    .width('100%')
    .height('100%')
  }
}

引入AR Engine

  1. 引入头文件。
#include "ar/ar_engine_core.h" 
  1. 编写CMakeLists.txt。
find_library(
    # Sets the name of the path variable.
    arengine-lib
    # Specifies the name of the NDK library that
    # you want CMake to locate.
    libarengine_ndk.z.so
)

target_link_libraries(entry PUBLIC
    ${arengine-lib}
)

创建AR场景

  1. 调用[HMS_AREngine_ARSession_Create]函数创建[AREngine_ARSession]会话。

  2. 配置AR会话及预览尺寸。

// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_ar_application.cpp
// 【可选】创建一个拥有合理默认配置的配置对象。
AREngine_ARConfig *arConfig = nullptr;
HMS_AREngine_ARConfig_Create(arSession, &arConfig);
// 【可选】配置AREngine_ARSession会话。
HMS_AREngine_ARSession_Configure(arSession, arConfig);
// 【可选】释放指定的配置对象的内存空间。
HMS_AREngine_ARConfig_Destroy(arConfig);

// 创建一个新的AREngine_ARFrame对象。
AREngine_ARFrame *arFrame = nullptr;
HMS_AREngine_ARFrame_Create(arSession, &arFrame);
// 预览区域的实际宽高,如使用xcomponent组件显示,则该宽和高是xcomponent的宽和高,如果不一致,会导致显示相机预览出错。
int32_t width = 1440;
int32_t height = 1080;
// 显示旋转常量,值为AREngine_ARPoseType中定义的枚举值。
AREngine_ARPoseType displayRotation = ARENGINE_POSE_TYPE_IDENTITY;
// 设置显示的宽和高(以像素为单位)。
HMS_AREngine_ARSession_SetDisplayGeometry(arSession, displayRotation, width, height);
  1. 通过openGL接口获取纹理ID
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_background_renderer.h。
//通过openGL接口获取纹理ID.
GLuint textureId = 0;
glGenTextures(1, &textureId);
  1. 设置openGL纹理,存储相机预览流数据。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_render_manager.cpp。
// 设置可用于存储相机预览流数据的openGL纹理。
HMS_AREngine_ARSession_SetCameraGLTexture(arSession, textureId );

获取平面

  1. 调用[HMS_AREngine_ARSession_Update]函数更新当前[AREngine_ARFrame]对象。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_render_manager.cpp。
// 获取帧数据AREngine_ARFrame。
HMS_AREngine_ARSession_Update(arSession, arFrame);
  1. 获取相机的视图矩阵和相机的投影矩阵,用于后续渲染。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_render_manager.cpp。
// 根据AREngine_ARFrame对象可以获取相机对象AREngine_ARCamera。
AREngine_ARCamera *arCamera = nullptr;
HMS_AREngine_ARFrame_AcquireCamera(arSession, arFrame, &arCamera);
// 获取最新帧中相机的视图矩阵。
HMS_AREngine_ARCamera_GetViewMatrix(arSession, arCamera, glm::value_ptr(*viewMat), 16);
// 获取用于在相机图像上层渲染虚拟内容的投影矩阵,可用于相机坐标系到裁剪坐标系转换。Near (0.1) Far (100)。
HMS_AREngine_ARCamera_GetProjectionMatrix(arSession, arCamera, {0.1f, 100.f}, glm::value_ptr(*projectionMat), 16);

说明

这里直接获取相机的视图矩阵和相机的投影矩阵,是为了便于渲染。获取相机运动中的位姿变化,还可以调用[HMS_AREngine_ARCamera_GetPose]函数配合[HMS_AREngine_ARPose_GetPoseRaw]函数进行获取。

  1. 调用[HMS_AREngine_ARSession_GetAllTrackables]函数获取平面列表。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_render_manager.cpp。
// 获取当前检测到的平面列表。
AREngine_ARTrackableList *planeList = nullptr;
// 创建一个可跟踪对象列表。
HMS_AREngine_ARTrackableList_Create(arSession, &planeList);
// 获取所有指定类型为ARENGINE_TRACKABLE_PLANE的可跟踪对像集合。
AREngine_ARTrackableType planeTrackedType = ARENGINE_TRACKABLE_PLANE;
HMS_AREngine_ARSession_GetAllTrackables(arSession, planeTrackedType, planeList);
int32_t planeListSize = 0;
// 获取此列表中的可跟踪对象的数量。
HMS_AREngine_ARTrackableList_GetSize(arSession, planeList, &planeListSize);
for (int i = 0; i < planeListSize; ++i) {
    AREngine_ARTrackable *arTrackable = nullptr;
    // 从可跟踪列表中获取指定index的对象。
    HMS_AREngine_ARTrackableList_AcquireItem(arSession, planeList, i, &arTrackable);
    AREngine_ARPlane *arPlane = reinterpret_cast<AREngine_ARPlane*>(arTrackable);
    // 获取当前可跟踪对象的跟踪状态。如果状态为:ARENGINE_TRACKING_STATE_TRACKING(可跟踪状态)才进行绘制。
    AREngine_ARTrackingState outTrackingState;
    HMS_AREngine_ARTrackable_GetTrackingState(arSession, arTrackable, &outTrackingState);
    AREngine_ARPlane *subsumePlane = nullptr;
    // 获取平面的父平面(一个平面被另一个平面合并时,会产生父平面),如果无父平面返回为NULL。
     HMS_AREngine_ARPlane_AcquireSubsumedBy(arSession, arPlane, &subsumePlane);
    if (subsumePlane != nullptr) {
        HMS_AREngine_ARTrackable_Release(reinterpret_cast<AREngine_ARTrackable*>(subsumePlane));
        // 如果当前平面有父平面,则当前平面不进行展示。否则会出现双平面。
        continue;
    }
    // 跟踪状态为:ARENGINE_TRACKING_STATE_TRACKING时才进行绘制。
    if (AREngine_ARTrackingState::ARENGINE_TRACKING_STATE_TRACKING != outTrackingState) {
        continue;
    }
    // 进行平面绘制。
}
HMS_AREngine_ARTrackableList_Destroy(planeList);
planeList = nullptr;
  1. 调用[HMS_AREngine_ARPlane_GetPolygon]函数获取平面的二维顶点坐标数组,用于绘制平面边界。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_plane_renderer.cpp。
// 获取检测到平面的二维顶点数组大小。
int32_t polygonLength = 0;
HMS_AREngine_ARPlane_GetPolygonSize(arSession, arPlane, &polygonLength);

// 获取检测到平面的二维顶点数组,格式为[x1,z1,x2,z2,...]。
const int32_t verticesSize = polygonLength / 2;
std::vector<glm::vec2> raw_vertices(verticesSize);
HMS_AREngine_ARPlane_GetPolygon(arSession, arPlane, glm::value_ptr(raw_vertices.front()), polygonLength);

// 局部坐标系顶点坐标。
for (int32_t i = 0; i < verticesSize; ++i) {
    vertices.emplace_back(raw_vertices[i].x, raw_vertices[i].y, 0.75f);
}

说明

调用[HMS_AREngine_ARPlane_GetPolygon]函数获取平面的二维顶点坐标数组格式为[x1,z1,x2,z2,…]。这些值均在平面局部坐标系的x-z平面中定义,须先调用[HMS_AREngine_ARPlane_GetCenterPose]函数获取从平面的局部坐标系到世界坐标系转换的位姿数据,然后调用[HMS_AREngine_ARPose_GetMatrix]函数将位姿数据转换成4X4的矩阵,该矩阵与局部坐标系的坐标点做乘法,可以得到局部坐标系到世界坐标系的转换。

  1. 将平面的二维顶点坐标转换到世界坐标系,并绘制平面。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_plane_renderer.cpp。
// 获取从平面的局部坐标系到世界坐标系转换的位姿信息。
AREngine_ARPose *scopedArPose = nullptr;
HMS_AREngine_ARPose_Create(arSession, nullptr, 0, &scopedArPose);
HMS_AREngine_ARPlane_GetCenterPose(arSession, arPlane, scopedArPose);

// 将位姿数据转换成4X4的矩阵,outMatrixColMajor4x4为存放数组,其中的数据按照列优先存储.
// 该矩阵与局部坐标系的坐标点做乘法,可以得到局部坐标系到世界坐标系的转换。
HMS_AREngine_ARPose_GetMatrix(arSession, scopedArPose, glm::value_ptr(modelMat), 16);
HMS_AREngine_ARPose_Destroy(scopedArPose);

// 构筑绘制渲染平面所需的数据。
// 生成三角形。
for (int i = 1; i < verticesSize - 1; ++i) {
    triangles.push_back(0);
    triangles.push_back(i);
    triangles.push_back(i + 1);
}
// 生成平面包围线。
for (int i = 0; i < verticesSize; ++i) {
    lines.push_back(i);
}

点击屏幕

  1. 用户点击屏幕后,基于点击事件获取屏幕坐标。

添加头文件:native_interface_xcomponent.h。

#include <ace/xcomponent/native_interface_xcomponent.h>

通过点击事件获取屏幕点击坐标。

// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_ar_application.cpp。
float pixeLX= 0.0f;
float pixeLY= 0.0f;
int32_t ret = OH_NativeXComponent_GetTouchEvent(component, window, &mTouchEvent);

if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {
    if (mTouchEvent.type == OH_NATIVEXCOMPONENT_DOWN) {
        pixeLX= mTouchEvent.touchPoints[0].x;
    pixeLY= mTouchEvent.touchPoints[0].y;
    } else {
    return;
    }
}
  1. 调用[HMS_AREngine_ARFrame_HitTest]函数进行碰撞检测,结果存放在碰撞检测结果列表中。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_ar_application.cpp。
// 创建一个命中检测结果对象列表,arSession为创建AR场景步骤中创建的会话对象。
AREngine_ARHitResultList *hitResultList = nullptr;
HMS_AREngine_ARHitResultList_Create(arSession, &hitResultList);

// 获取命中检测结果对象列表,arFrame为创建AR场景步骤中创建的帧对象,pixeLX/pixeLY为屏幕点坐标。
HMS_AREngine_ARFrame_HitTest(arSession, arFrame, pixeLX, pixeLY, hitResultList);

说明

碰撞结果按照交点与设备的距离从近到远进行排序,存放在碰撞结果列表中。

放置虚拟物体

  1. 调用[HMS_AREngine_ARHitResultList_GetItem]函数遍历碰撞检测结果列表,获取命中的可跟踪对象。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_ar_application.cpp。
// 创建命中检测结果对象。
AREngine_ARHitResult *arHit = nullptr;
HMS_AREngine_ARHitResult_Create(arSession, &arHit);

// 获取第一个命中检测结果对象。
HMS_AREngine_ARHitResultList_GetItem(arSession, hitResultList, 0, arHit);

// 获取被命中的可追踪对象。
AREngine_ARTrackable *arHitTrackable = nullptr;
HMS_AREngine_ARHitResult_AcquireTrackable(arSession, arHit, &arHitTrackable);
  1. 判断碰撞结果是否存在于平面内部。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_ar_application.cpp。
AREngine_ARTrackableType ar_trackable_type = ARENGINE_TRACKABLE_INVALID;
HMS_AREngine_ARTrackable_GetType(arSession, arTrackable, &ar_trackable_type);
if (ARENGINE_TRACKABLE_PLANE == ar_trackable_type) {
    AREngine_ARPose *arPose = nullptr;
    HMS_AREngine_ARPose_Create(arSession, nullptr, 0, &arPose);
    HMS_AREngine_ARHitResult_GetHitPose(arSession, arHit, arPose);
    // 判断位姿是否位于平面的多边形范围内。0表示不在范围内,非0表示在范围内。
    HMS_AREngine_ARPlane_IsPoseInPolygon(arSession, arPlane, arPose, &inPolygon);
    HMS_AREngine_ARPose_Destroy(arPose);
    if (!inPolygon) {
    // 不在平面内,就跳过当前平面。
    continue;
    }
}
  1. 在碰撞结果位置创建一个新的锚点,并基于此锚点放置虚拟模型。
// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_ar_application.cpp。
// 在碰撞命中位置创建一个新的锚点。
AREngine_ARAnchor *anchor = nullptr;
HMS_AREngine_ARHitResult_AcquireNewAnchor(arSession, arHitResult, &anchor);

// 判断锚点的可跟踪状态
AREngine_ARTrackingState trackingState = ARENGINE_TRACKING_STATE_STOPPED;
HMS_AREngine_ARAnchor_GetTrackingState(arSession, anchor, &trackingState);
if (trackingState != ARENGINE_TRACKING_STATE_TRACKING) {
    HMS_AREngine_ARAnchor_Release(anchor);
    return;
}


  1. 绘制模型。

调用[HMS_AREngine_ARAnchor_GetPose]函数获取锚点位姿,并基于该位姿绘制虚拟模型。

// 此代码来源于示例代码:ArSample/entry/src/main/cpp/src/world/world_render_manager.cpp。
// 获取锚点的位姿。
AREngine_ARPose *pose = nullptr;
HMS_AREngine_ARPose_Create(arSession, nullptr, 0, &pose);
HMS_AREngine_ARAnchor_GetPose(arSession, anchor, pose);
// 将位姿数据转换成4X4的矩阵modelMat。
HMS_AREngine_ARPose_GetMatrix(arSession, pose, glm::value_ptr(modelMat), 16);
HMS_AREngine_ARPose_Destroy(pose);
// 绘制虚拟模型。

最后呢

很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

在这里插入图片描述

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细鸿蒙(OpenHarmony )手册(共计1236页)与鸿蒙(OpenHarmony )开发入门视频,帮助大家在技术的道路上更进一步。

  • 《鸿蒙 (OpenHarmony)开发学习视频》
  • 《鸿蒙生态应用开发V2.0白皮书》
  • 《鸿蒙 (OpenHarmony)开发基础到实战手册》
  • OpenHarmony北向、南向开发环境搭建
  • 《鸿蒙开发基础》
  • 《鸿蒙开发进阶》
  • 《鸿蒙开发实战》

在这里插入图片描述

总结

鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿
1

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

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

相关文章

刷题记录(2)

1. HWOD机试 - 模拟消息队列(100) package com.yue.test;import org.junit.Test;import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List;/*** Author: 夜雨* Date: 2021-12-08-10:31* Description:* Version 1.0*/ public…

C#编译成32和64位的区别

C#编译成32和64位的区别 背景 C#32位客户端项目在把代码提交到客户端之后&#xff0c;jinkens直接崩掉了。原因是内存占用100%运维同学建议改成64位&#xff0c;理由是电脑内存大&#xff0c;客观条件IT不给扩。那么在同一台电脑上&#xff0c;32位和64位在编译过程中有什么区…

【DEV工具-IDEA】idea的光标变成黑块了?

项目场景&#xff1a; 解决&#xff1a;windows&#xff1a;按一下insert键。

Python获取次幂数据公众号榜单数据

公众号排行榜,wx公众号排行榜,原创排行榜,赞赏排行榜,评论排行榜 教程仅供参考,请勿滥用,由此带来的法律责任,需由自己承担。 一、运行效果 二、程序代码 #!/usr/bin/python # -*- coding: UTF-8 -*- """ @author: Roc-xb """import request…

Java学习第六天

Java进阶知识面向对象 static&#xff1a;是静态的意思&#xff0c;可以修饰成员变量&#xff0c;表示该成员变量在内存中只存储一份&#xff0c;可以被共享访问。 静态成员变量&#xff08;有static修饰&#xff0c;属于类&#xff0c;内存中加载一次&#xff09;&#xff1a…

三元里等你!融合三个经典模型!Transformer-LSTM-SVM多变量时间序列预测(Matlab)

三元里等你&#xff01;融合三个经典模型&#xff01;Transformer-LSTM-SVM多变量时间序列预测&#xff08;Matlab&#xff09; 目录 三元里等你&#xff01;融合三个经典模型&#xff01;Transformer-LSTM-SVM多变量时间序列预测&#xff08;Matlab&#xff09;效果一览基本介…

I2C总线的标准收发代码

结合I2C总线协议的知识&#xff0c;我们可以知道I2C写数据由一下10个步骤组成。 第一步&#xff0c;发送一个起始信号。 第二步&#xff0c;发送7bit从机地址&#xff0c;即OZ9350的地址。此处需要注意&#xff0c;发送数据时&#xff0c;无法发送7bit数据&#xff0c;此处发…

求和放大器(单位/非单位增益加法器+比例加法器)+运算放大器实现积分器和微分器

2024-9-2&#xff0c;星期一&#xff0c;22:00&#xff0c;天气&#xff1a;晴转雨&#xff0c;心情&#xff1a;晴。新的一周开始了&#xff0c;新的一个月又开始啦&#xff0c;希望大家开开心心&#xff0c;以崭新的面貌迎接中秋和十一假期&#xff01;废话不多说&#xff0c…

LinkAI工作流支持广场访问和api调用啦

什么是工作流 LinkAI工作流&#xff08;WorkFlow&#xff09;是一种灵活的智能体搭建方式。可以自由选择「大模型、应用、知识库、插件、意图识别、转人工、渠道消息发送」等多种原子能力&#xff0c;通过可视化拖拉拽的方式进行组合编排&#xff0c;零代码搭出一个业务流程。…

PPT制作加速器:3款工具插件的演示文稿制作更高效

IvyhTools英豪插件 IvyhTools是一款功能强大的PPT插件&#xff0c;主要用于辅助用户进行各种PPT编辑和处理操作。该插件具备以下主要功能&#xff1a; 字体编辑&#xff1a;用户可以对PPT中的字体进行编辑和调整。 动图录制&#xff1a;支持录制动态图像&#xff0c;方便用户在…

深度学习(四)-卷积神经网络

神经网络局限 不考虑数据形状 未考虑数据的“形状”&#xff0c;会破坏数据空间结构。例如&#xff0c;输入数据是图像时&#xff0c;图像通常是高长通道方向上的3维形状。但是&#xff0c;向全连接层输入时&#xff0c;需要将3维数据拉平为1维数据 参数庞大 全连接网络参数…

中小企业怎么选择MES:专用MES、集成MES和可配置MES

专用MES、集成MES和可配置MES是MES&#xff08;制造执行系统&#xff09;在不同发展阶段和应用场景下的三种主要形式。它们各自具有不同的特点和应用优势&#xff0c;下面将分别进行详细介绍。 专用MES 定义与特点&#xff1a; 专用MES是针对特定行业或特定生产流程而设计的…

CCS报错:error: cannot find file “libc.a“+CCS安装包

1、编译工程出现报错以下报错信息&#xff1a; error: cannot find file "libc.a" warning: automatic RTS selection: attempt to automatically link in index library "libc.a" failed; file not found warning: entry-point symbol "_c_int0…

新剧震撼登场,首集飙到9.2分,观众无不惊叹

自2022年《弹子球游戏》首季发布以来&#xff0c;它以其无与伦比的电影质感和精湛的双线叙事手法&#xff0c;让人印象深刻。这部Apple TV出品的剧集&#xff0c;改编自作家李敏金的小说《柏青哥》&#xff0c;讲述了四代移民的艰辛故事。它不仅仅是一部剧集&#xff0c;更是一…

RuoYi-Cloud 部署与配置 [CentOS7]

静态IP设置 # 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33# 修改文件内容 TYPEEthernet PROXY_METHODnone BROWSER_ONLYno BOOTPROTOstatic IPADDR192.168.18.130 NETMASK255.255.255.0 GATEWAY192.168.18.2 DEFROUTEyes IPV4_FAILURE_FATALno IPV6INIT…

电脑屏幕监控软件有哪些?10款真实好用的电脑屏幕监控软件 | 超全盘点,不容错过!

"千里眼&#xff0c;顺风耳&#xff0c;世间万事皆能察。" 在当今数字化时代&#xff0c;却有了现实版的映射——电脑屏幕监控软件&#xff0c;这些软件如同企业的“千里眼”&#xff0c;能够实时洞察员工的电脑使用情况&#xff0c;确保信息安全&#xff0c;提升工…

动态规划法-资源分配问题

动态规划法 - 资源分配问题 问题描述 把4个份额的资源分配给3个工程&#xff0c;给定利润表如下表所示&#xff0c;写出资源的最优分配方案的求解过程。 4份资源分配给3个工程的利润表 步骤一&#xff1a;求各个阶段不同分配份额时的最大利润及分配份额 目标 我们的目标是…

加速电商物流效率:推荐几款实用的快递批量查询工具

做电商必不可少的快递批量查询平台分享&#xff1a;固乔快递查询助手使用全攻略 在电商行业日益竞争的今天&#xff0c;高效管理物流信息成为了商家们不可或缺的一环。面对每天成百上千的订单和快递单号&#xff0c;如何快速、准确地查询并跟踪物流状态&#xff0c;成为了电商…

SpringBoot的配置文件详解

SpringBoot配置文件概述 1&#xff09;SpringBoot的配置文件的名字必须以application开头&#xff1a; 2&#xff09;SpringBoot配置文件有两种后缀&#xff1a;.properties和.yml&#xff08;表示的意思是一样&#xff0c;只是编写数据的格式不同&#xff09; application.p…

Pixelmator Pro for Mac 专业图像处理软件【媲美PS的修图软件】

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件…