HarmonyOS Next开发学习手册——Native XComponent

场景介绍

Native XComponent是XComponent组件提供在Native层的实例,可作为JS层和Native层XComponent绑定的桥梁。XComponent所提供的NDK接口都依赖于该实例。接口能力包括获取Native Window实例、获取XComponent的布局/事件信息、注册XComponent的生命周期回调、注册XComponent的触摸、鼠标、按键等事件回调。针对Native XComponent,主要的开发场景如下:

  • 利用Native XComponent提供的接口注册XComponent的生命周期和事件回调。
  • 在这些回调中进行初始化环境、获取当前状态、响应各类事件的开发。
  • 利用Native Window和EGL接口开发自定义绘制内容以及申请和提交Buffer到图形队列。

接口说明

接口名描述
OH_NativeXComponent_GetXComponentId(OH_NativeXComponent* component, char* id, uint64_t* size)获取XComponent的id。
OH_NativeXComponent_GetXComponentSize(OH_NativeXComponent* component, const void* window, uint64_t* width, uint64_t* height)获取XComponent持有的surface的大小。
OH_NativeXComponent_GetXComponentOffset(OH_NativeXComponent* component, const void* window, double* x, double* y)获取XComponent持有的surface相对其父组件左顶点的偏移量。
OH_NativeXComponent_GetTouchEvent(OH_NativeXComponent* component, const void* window, OH_NativeXComponent_TouchEvent* touchEvent)获取由XComponent触发的触摸事件。touchEvent内的具体属性值可参考OH_NativeXComponent_TouchEvent。
OH_NativeXComponent_GetTouchPointToolType(OH_NativeXComponent* component, uint32_t pointIndex, OH_NativeXComponent_TouchPointToolType* toolType)获取XComponent触摸点的工具类型。
OH_NativeXComponent_GetTouchPointTiltX(OH_NativeXComponent* component, uint32_t pointIndex, float* tiltX)获取XComponent触摸点处相对X轴的倾斜角度。
OH_NativeXComponent_GetTouchPointTiltY(OH_NativeXComponent* component, uint32_t pointIndex, float* tiltY)获取XComponent触摸点处相对Y轴的倾斜角度。
OH_NativeXComponent_GetMouseEvent(OH_NativeXComponent* component, const void* window, OH_NativeXComponent_MouseEvent* mouseEvent)获取由XComponent触发的鼠标事件。
OH_NativeXComponent_RegisterCallback(OH_NativeXComponent* component, OH_NativeXComponent_Callback* callback)为此OH_NativeXComponent实例注册生命周期和触摸事件回调。
OH_NativeXComponent_RegisterMouseEventCallback(OH_NativeXComponent* component, OH_NativeXComponent_MouseEvent_Callback* callback)为此OH_NativeXComponent实例注册鼠标事件回调。
OH_NativeXComponent_RegisterFocusEventCallback(OH_NativeXComponent* component, void (callback)(OH_NativeXComponent component, void* window))为此OH_NativeXComponent实例注册获得焦点事件回调。
OH_NativeXComponent_RegisterKeyEventCallback(OH_NativeXComponent* component, void (callback)(OH_NativeXComponent component, void* window))为此OH_NativeXComponent实例注册按键事件回调。
OH_NativeXComponent_RegisterBlurEventCallback(OH_NativeXComponent* component, void (callback)(OH_NativeXComponent component, void* window))为此OH_NativeXComponent实例注册失去焦点事件回调。
OH_NativeXComponent_GetKeyEvent(OH_NativeXComponent* component, OH_NativeXComponent_KeyEvent** keyEvent)获取由XComponent触发的按键事件。
OH_NativeXComponent_GetKeyEventAction(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_KeyAction* action)获取按键事件的动作。
OH_NativeXComponent_GetKeyEventCode(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_KeyCode* code)获取按键事件的键码值。
OH_NativeXComponent_GetKeyEventSourceType(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_EventSourceType* sourceType)获取按键事件的输入源类型。
OH_NativeXComponent_GetKeyEventDeviceId(OH_NativeXComponent_KeyEvent* keyEvent, int64_t* deviceId)获取按键事件的设备ID。
OH_NativeXComponent_GetKeyEventTimestamp(OH_NativeXComponent_KeyEvent* keyEvent, int64_t* timestamp)获取按键事件的时间戳。

生命周期说明

开发者在ArkTS侧使用如下代码即可用XComponent组件进行利用EGL/OpenGLES渲染的开发。

@Builder
function myComponent() {XComponent({ id: 'xcomponentId1', type: 'surface', libraryname: 'nativerender' }).onLoad((context) => {}).onDestroy(() => {})
}

onLoad事件

触发时刻:XComponent准备好surface后触发。

参数context:其上面挂载了暴露在模块上的Native方法,使用方法类似于利用 import context from “libnativerender.so” 直接加载模块后获得的context实例。

时序:onLoad事件的触发和surface相关,其和Native侧的OnSurfaceCreated的时序如下图:

onDestroy事件

触发时刻:XComponent组件被销毁时触发与一般ArkUI的组件销毁时机一致,其和Native侧的OnSurfaceDestroyed的时序如下图:

开发步骤

以下步骤描述了如何使用XComponent组件调用NAPI接口来创建EGL/GLES环境,实现在主页面绘制图形,并可以改变图形的颜色。

  1. 在界面中定义XComponent
@Entry
@Component
struct Index {@State message: string = 'Hello World'xComponentContext: object | undefined = undefined;xComponentAttrs: XComponentAttrs = {id: 'xcomponentId',type: XComponentType.SURFACE,libraryname: 'nativerender'}build() {Row() {// ...// 在xxx.ets 中定义 XComponentXComponent(this.xComponentAttrs).focusable(true) // 可响应键盘事件.onLoad((xComponentContext) => {this.xComponentContext = xComponentContext;}).onDestroy(() => {console.log("onDestroy");})// ...}.height('100%')}
}interface XComponentAttrs {id: string;type: number;libraryname: string;
}
  1. Napi模块注册,具体使用请参考 Native API在应用工程中的使用指导 。
// 在napi_init.cpp文件中,Init方法注册接口函数,从而将封装的C++方法传递出来,供JS侧调用
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports)
{// ...// 向JS侧暴露接口getContext()napi_property_descriptor desc[] = {{ "getContext", nullptr, PluginManager::GetContext, nullptr, nullptr, nullptr, napi_default, nullptr }};if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) {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;
}
EXTERN_C_END// 编写接口的描述信息,根据实际需要可以修改对应参数
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 }
};// __attribute__((constructor))修饰的方法由系统自动调用,使用NAPI接口napi_module_register()传入模块描述信息进行模块注册
extern "C" __attribute__((constructor)) void RegisterModule(void)
{napi_module_register(&nativerenderModule);
}// 使用NAPI中的napi_define_properties方法,向JS侧暴露drawPattern()方法,在JS侧调用drawPattern()来绘制内容。
void PluginRender::Export(napi_env env, napi_value exports)
{// ...// 将接口函数注册为JS侧接口drawPatternnapi_property_descriptor desc[] = {{ "drawPattern", nullptr, PluginRender::NapiDrawPattern, nullptr, nullptr, nullptr, napi_default, nullptr }};if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "Export: napi_define_properties failed");}
}
  1. 注册XComponent事件回调,使用NAPI实现XComponent事件回调函数。

    (1) 定义surface创建成功,发生改变,销毁和XComponent的touch事件回调接口。

// 定义一个函数OnSurfaceCreatedCB(),封装初始化环境与绘制背景
void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window)
{// ...// 获取XComponent的id,即JS侧XComponent组件构造中的id参数char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback","OnSurfaceCreatedCB: Unable to get XComponent id");return;}// 初始化环境与绘制背景std::string id(idStr);auto render = PluginRender::GetInstance(id);uint64_t width;uint64_t height;// 获取XComponent拥有的surface的大小int32_t xSize = OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);if ((xSize == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) && (render != nullptr)) {if (render->eglCore_->EglContextInit(window, width, height)) {render->eglCore_->Background();}}
}// 定义一个函数OnSurfaceChangedCB()
void OnSurfaceChangedCB(OH_NativeXComponent *component, void *window)
{// ...// 获取XComponent的idchar idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Callback","OnSurfaceChangedCB: Unable to get XComponent id");return;}std::string id(idStr);auto render = PluginRender::GetInstance(id);if (render != nullptr) {// 封装OnSurfaceChanged方法render->OnSurfaceChanged(component, window);}
}// 定义一个函数OnSurfaceDestroyedCB(),将PluginRender类内释放资源的方法Release()封装在其中
void OnSurfaceDestroyedCB(OH_NativeXComponent *component, void *window)
{// ...// 获取XComponent的idchar idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {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);
}// 定义一个函数DispatchTouchEventCB(),响应触摸事件时触发该回调
void DispatchTouchEventCB(OH_NativeXComponent *component, void *window)
{// ...// 获取XComponent的idchar idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = { '\0' };uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {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 (render != nullptr) {// 封装OnTouchEvent方法render->OnTouchEvent(component, window);}
}// 定义一个函数DispatchMouseEventCB(),响应鼠标事件时触发该回调
void DispatchMouseEventCB(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchMouseEventCB");int32_t ret;char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {return;}std::string id(idStr);auto render = PluginRender::GetInstance(id);if (render) {// 封装OnMouseEvent方法render->OnMouseEvent(component, window);}
}// 定义一个函数DispatchHoverEventCB(),响应鼠标悬停事件时触发该回调
void DispatchHoverEventCB(OH_NativeXComponent *component, bool isHover) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "DispatchHoverEventCB");int32_t ret;char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {return;}std::string id(idStr);auto render = PluginRender::GetInstance(id);if (render) {// 封装OnHoverEvent方法render->OnHoverEvent(component, isHover);}
}// 定义一个函数OnFocusEventCB(),响应获焦事件时触发该回调
void OnFocusEventCB(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnFocusEventCB");int32_t ret;char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {return;}std::string id(idStr);auto render = PluginRender::GetInstance(id);if (render) {// 封装OnFocusEvent方法render->OnFocusEvent(component, window);}
}// 定义一个函数OnBlurEventCB(),响应失去焦点事件时触发该回调
void OnBlurEventCB(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnBlurEventCB");int32_t ret;char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {return;}std::string id(idStr);auto render = PluginRender::GetInstance(id);if (render) {// 封装OnBlurEvent方法render->OnBlurEvent(component, window);}
}// 定义一个函数OnKeyEventCB(),响应按键事件时触发该回调
void OnKeyEventCB(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "Callback", "OnKeyEventCB");int32_t ret;char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {};uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1;ret = OH_NativeXComponent_GetXComponentId(component, idStr, &idSize);if (ret != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {return;}std::string id(idStr);auto render = PluginRender::GetInstance(id);if (render) {// 封装OnKeyEvent方法render->OnKeyEvent(component, window);}
}// 定义一个OnSurfaceChanged()方法
void PluginRender::OnSurfaceChanged(OH_NativeXComponent* component, void* window)
{// ...std::string id(idStr);PluginRender* render = PluginRender::GetInstance(id);double offsetX;double offsetY;// 获取XComponent持有的surface相对其父组件左顶点的偏移量OH_NativeXComponent_GetXComponentOffset(component, window, &offsetX, &offsetY);OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OH_NativeXComponent_GetXComponentOffset","offsetX = %{public}lf, offsetY = %{public}lf", offsetX, offsetY);uint64_t width;uint64_t height;OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);if (render != nullptr) {render->eglCore_->UpdateSize(width, height);}
}// 定义一个OnTouchEvent()方法
void PluginRender::OnTouchEvent(OH_NativeXComponent* component, void* window)
{// ...OH_NativeXComponent_TouchEvent touchEvent;// 获取由XComponent触发的触摸事件OH_NativeXComponent_GetTouchEvent(component, window, &touchEvent);// 获取XComponent触摸点相对于XComponent组件左边缘的坐标x和相对于XComponent组件上边缘的坐标yOH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OnTouchEvent","touch info: x = %{public}lf, y = %{public}lf", touchEvent.x, touchEvent.y);// 获取XComponent触摸点相对于XComponent所在应用窗口左上角的x坐标和相对于XComponent所在应用窗口左上角的y坐标OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OnTouchEvent","touch info: screenX = %{public}lf, screenY = %{public}lf", touchEvent.screenX, touchEvent.screenY);std::string id(idStr);PluginRender* render = PluginRender::GetInstance(id);if (render != nullptr && touchEvent.type == OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_UP) {render->eglCore_->ChangeColor();hasChangeColor_ = 1;}float tiltX = 0.0f;float tiltY = 0.0f;OH_NativeXComponent_TouchPointToolType toolType =OH_NativeXComponent_TouchPointToolType::OH_NATIVEXCOMPONENT_TOOL_TYPE_UNKNOWN;// 获取XComponent触摸点的工具类型OH_NativeXComponent_GetTouchPointToolType(component, 0, &toolType);// 获取XComponent触摸点处相对X轴的倾斜角度OH_NativeXComponent_GetTouchPointTiltX(component, 0, &tiltX);// 获取XComponent触摸点处相对Y轴的倾斜角度OH_NativeXComponent_GetTouchPointTiltY(component, 0, &tiltY);OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "OnTouchEvent","touch info: toolType = %{public}d, tiltX = %{public}lf, tiltY = %{public}lf", toolType, tiltX, tiltY);
}// 定义一个OnMouseEvent()方法
void PluginRender::OnMouseEvent(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "OnMouseEvent");OH_NativeXComponent_MouseEvent mouseEvent;// 获取由XComponent触发的鼠标事件int32_t ret = OH_NativeXComponent_GetMouseEvent(component, window, &mouseEvent);if (ret == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "MouseEvent Info: x = %{public}f, y = %{public}f, action = %{public}d, button = %{public}d", mouseEvent.x, mouseEvent.y, mouseEvent.action, mouseEvent.button);} else {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "GetMouseEvent error");}
}// 定义一个OnMouseEvent()方法
void PluginRender::OnKeyEvent(OH_NativeXComponent *component, void *window) {OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "OnKeyEvent");OH_NativeXComponent_KeyEvent *keyEvent = nullptr;// 获取由XComponent触发的按键事件。if (OH_NativeXComponent_GetKeyEvent(component, &keyEvent) >= 0) {OH_NativeXComponent_KeyAction action;// 获取按键事件的动作OH_NativeXComponent_GetKeyEventAction(keyEvent, &action);OH_NativeXComponent_KeyCode code;// 获取按键事件的键码值OH_NativeXComponent_GetKeyEventCode(keyEvent, &code);OH_NativeXComponent_EventSourceType sourceType;// 获取按键事件的输入源类型OH_NativeXComponent_GetKeyEventSourceType(keyEvent, &sourceType);int64_t deviceId;// 获取按键事件的设备IDOH_NativeXComponent_GetKeyEventDeviceId(keyEvent, &deviceId);int64_t timeStamp;// 获取按键事件的时间戳OH_NativeXComponent_GetKeyEventTimestamp(keyEvent, &timeStamp);OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "KeyEvent Info: action=%{public}d, code=%{public}d, sourceType=%{public}d, deviceId=%{public}ld, timeStamp=%{public}ld", action, code, sourceType, deviceId, timeStamp);} else {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "GetKeyEvent error");}
}

(2) 注册XComponent事件回调函数,在XComponent事件触发时调用3.1步骤中定义的方法。

void PluginRender::RegisterCallback(OH_NativeXComponent *nativeXComponent) {// 设置组件创建事件的回调函数,组件创建时触发相关操作,初始化环境与绘制背景renderCallback_.OnSurfaceCreated = OnSurfaceCreatedCB;// 设置组件改变事件的回调函数,组件改变时触发相关操作renderCallback_.OnSurfaceChanged = OnSurfaceChangedCB;// 设置组件销毁事件的回调函数,组件销毁时触发相关操作,释放申请的资源renderCallback_.OnSurfaceDestroyed = OnSurfaceDestroyedCB;// 设置触摸事件的回调函数,在触摸事件触发时调用NAPI接口函数,从而调用原C++方法renderCallback_.DispatchTouchEvent = DispatchTouchEventCB;// 将OH_NativeXComponent_Callback注册给NativeXComponentOH_NativeXComponent_RegisterCallback(nativeXComponent, &renderCallback_);// 设置鼠标事件的回调函数,在触摸事件触发时调用NAPI接口函数,从而调用原C++方法mouseCallback_.DispatchMouseEvent = DispatchMouseEventCB;// 设置鼠标悬停事件的回调函数,在触摸事件触发时调用NAPI接口函数,从而调用原C++方法mouseCallback_.DispatchHoverEvent = DispatchHoverEventCB;// 将OH_NativeXComponent_MouseEvent_Callback注册给NativeXComponentOH_NativeXComponent_RegisterMouseEventCallback(nativeXComponent, &mouseCallback_);// 将OnFocusEventCB方法注册给NativeXComponentOH_NativeXComponent_RegisterFocusEventCallback(nativeXComponent, OnFocusEventCB);// 将OnKeyEventCB方法注册给NativeXComponentOH_NativeXComponent_RegisterKeyEventCallback(nativeXComponent, OnKeyEventCB);// 将OnBlurEventCB方法注册给 NativeXComponentOH_NativeXComponent_RegisterBlurEventCallback(nativeXComponent, OnBlurEventCB);
}

(3) 定义NapiDrawPattern方法,暴露到JS侧的drawPattern()方法会执行该方法。

napi_value PluginRender::NapiDrawPattern(napi_env env, napi_callback_info info)
{// ...// 获取环境变量参数napi_value thisArg;if (napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr) != napi_ok) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "NapiDrawPattern: napi_get_cb_info fail");return nullptr;}// 获取环境变量中XComponent实例napi_value exportInstance;if (napi_get_named_property(env, thisArg, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender","NapiDrawPattern: napi_get_named_property fail");return nullptr;}// 通过napi_unwrap接口,获取XComponent的实例指针OH_NativeXComponent *nativeXComponent = nullptr;if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender", "NapiDrawPattern: 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_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginRender","NapiDrawPattern: Unable to get XComponent id");return nullptr;}std::string id(idStr);PluginRender *render = PluginRender::GetInstance(id);if (render) {// 调用绘制方法render->eglCore_->Draw();OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginRender", "render->eglCore_->Draw() executed");}return nullptr;
}
  1. 初始化环境,包括初始化可用的EGLDisplay、确定可用的surface配置、创建渲染区域surface、创建并关联上下文等。
void EGLCore::UpdateSize(int width, int height) 
{width_ = width;height_ = height;if (width_ > 0) {// 计算绘制矩形宽度百分比width_Percent_ = FIFTY_PERCENT * height_ / width_;}
}bool EGLCore::EglContextInit(void *window, int width, int height)
{// ...UpdateSize(width, height);eglWindow_ = static_cast<EGLNativeWindowType>(window);// 初始化displayeglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY);if (eglDisplay_ == EGL_NO_DISPLAY) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglGetDisplay: unable to get EGL display");return false;}// 初始化EGLEGLint majorVersion;EGLint minorVersion;if (!eglInitialize(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(eglDisplay_, ATTRIB_LIST, &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()
{// ...// 创建surfaceeglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL);// ...// 创建contexteglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS);if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglMakeCurrent failed");return false;}// 创建programprogram_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER);if (program_ == PROGRAM_ERROR) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "CreateProgram: unable to create program");return false;}return true;
}
  1. 渲染功能实现

(1) 绘制背景。

// 绘制背景颜色 #f4f4f4
const GLfloat BACKGROUND_COLOR[] = { 244.0f / 255, 244.0f / 255, 244.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
};

// 绘制背景颜色
void EGLCore::Background()
{GLint position = PrepareDraw();if (position == POSITION_ERROR) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Background 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", "Background execute draw failed");return;}if (!FinishDraw()) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Background FinishDraw failed");return;}
}// 绘前准备,获取position,创建成功时position值从0开始
GLint EGLCore::PrepareDraw()
{if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (eglContext_ == nullptr) ||(!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "PrepareDraw: param error");return POSITION_ERROR;}glViewport(DEFAULT_X_POSITION, DEFAULT_Y_POSITION, width_, height_);glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT);glClear(GL_COLOR_BUFFER_BIT);glUseProgram(program_);return glGetAttribLocation(program_, POSITION_NAME);
}// 依据传入参数在指定区域绘制指定颜色
bool EGLCore::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[],unsigned long vertSize)
{if ((position > 0) || (color == nullptr) || (vertSize / sizeof(shapeVertices[0]) != SHAPE_VERTICES_SIZE)) {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, shapeVertices);glEnableVertexAttribArray(position);glVertexAttrib4fv(1, color);glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE);glDisableVertexAttribArray(position);return true;
}// 结束绘制操作
bool EGLCore::FinishDraw()
{// 强制刷新缓冲glFlush();glFinish();return eglSwapBuffers(eglDisplay_, eglSurface_);
}

(2) 绘制图形。

void EGLCore::Draw()
{flag_ = false;OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "Draw");GLint position = PrepareDraw();if (position == POSITION_ERROR) {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;}// 将五角星分为五个四边形,计算其中一个四边形的四个顶点GLfloat rotateX = 0;GLfloat rotateY = FIFTY_PERCENT * height_;GLfloat centerX = 0;GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18);GLfloat leftX = -rotateY * (M_PI / 180 * 18);GLfloat leftY = 0;GLfloat rightX = rotateY * (M_PI / 180 * 18);GLfloat rightY = 0;// 确定绘制四边形的顶点,使用绘制区域的百分比表示const GLfloat shapeVertices[] = {centerX / width_, centerY / height_,leftX / width_, leftY / height_,rotateX / width_, rotateY / height_,rightX / width_, rightY / height_};if (!ExecuteDrawStar(position, DRAW_COLOR, shapeVertices, sizeof(shapeVertices))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw star failed");return;}GLfloat rad = M_PI / 180 * 72;for (int i = 0; i < 4; ++i) {// 旋转得其他四个四边形的顶点rotate2d(centerX, centerY, &rotateX, &rotateY,rad);rotate2d(centerX, centerY, &leftX, &leftY,rad);rotate2d(centerX, centerY, &rightX, &rightY,rad);// 确定绘制四边形的顶点,使用绘制区域的百分比表示const GLfloat shapeVertices[] = {centerX / width_, centerY / height_,leftX / width_, leftY / height_,rotateX / width_, rotateY / height_,rightX / width_, rightY / height_};// 绘制图形if (!ExecuteDrawStar(position, DRAW_COLOR, shapeVertices, sizeof(shapeVertices))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw star failed");return;}}// 结束绘制if (!FinishDraw()) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw FinishDraw failed");return;}flag_ = true;
}

(3) 改变颜色,重新画一个大小相同颜色不同的图形,与原图形替换,达到改变颜色的效果。

void EGLCore::ChangeColor()
{if (!flag_) {return;}OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor");GLint position = PrepareDraw();if (position == POSITION_ERROR) {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;}// 确定绘制四边形的顶点,使用绘制区域的百分比表示GLfloat rotateX = 0;GLfloat rotateY = FIFTY_PERCENT * height_;GLfloat centerX = 0;GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18);GLfloat leftX = -rotateY * (M_PI / 180 * 18);GLfloat leftY = 0;GLfloat rightX = rotateY * (M_PI / 180 * 18);GLfloat rightY = 0;// 确定绘制四边形的顶点,使用绘制区域的百分比表示const GLfloat shapeVertices[] = {centerX / width_, centerY / height_,leftX / width_, leftY / height_,rotateX / width_, rotateY / height_,rightX / width_, rightY / height_};// 使用新的颜色绘制if (!ExecuteDrawStar2(position, CHANGE_COLOR, shapeVertices, sizeof(shapeVertices))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw star failed");return;}GLfloat rad = M_PI / 180 * 72;for (int i = 0; i < 4; ++i){// 旋转得其他四个四边形的顶点rotate2d(centerX, centerY, &rotateX, &rotateY,rad);rotate2d(centerX, centerY, &leftX, &leftY,rad);rotate2d(centerX, centerY, &rightX, &rightY,rad);// 确定绘制四边形的顶点,使用绘制区域的百分比表示const GLfloat shapeVertices[] = {centerX / width_, centerY / height_,leftX / width_, leftY / height_,rotateX / width_, rotateY / height_,rightX / width_, rightY / height_};// 使用新的颜色绘制if (!ExecuteDrawStar2(position, CHANGE_COLOR, shapeVertices, sizeof(shapeVertices))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw star failed");return;}}// 结束绘制if (!FinishDraw()) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor FinishDraw failed");}
}
  1. 释放相关资源

(1) EGLCore类下创建Release()方法,释放初始化环境时申请的资源,包含窗口display、渲染区域surface、环境上下文context等。

void EGLCore::Release()
{// 释放surfaceif ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroySurface failed");}// 释放contextif ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroyContext failed");}// 释放displayif ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) {OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglTerminate failed");}
}

(2) PluginRender类添加Release()方法,释放EGLCore实例及PluginRender实例。

void PluginRender::Release(std::string &id)
{PluginRender *render = PluginRender::GetInstance(id);if (render != nullptr) {render->eglCore_->Release();delete render->eglCore_;render->eglCore_ = nullptr;delete render;render = nullptr;instance_.erase(instance_.find(id));}
}
  1. CMakeLists,使用CMake工具链将C++源代码编译成动态链接库文件。
# 设置CMake最小版本
cmake_minimum_required(VERSION 3.4.1)
# 项目名称
project(XComponent)set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
add_definitions(-DOHOS_PLATFORM)
# 设置头文件搜索目录
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
)find_library(EGL-libEGL
)find_library(GLES-libGLESv3
)find_library(hilog-libhilog_ndk.z
)find_library(libace-libace_ndk.z
)find_library(libnapi-libace_napi.z
)find_library(libuv-libuv
)
# 添加构建需要链接的库
target_link_libraries(nativerender PUBLIC${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib})

鸿蒙全栈开发全新学习指南

为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

在这里插入图片描述

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

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

开发基础知识:gitee.com/MNxiaona/733GH

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

基于ArkTS 开发

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

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

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

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

相关文章

.net8 Syncfusion生成pdf/doc/xls/ppt最新版本

新建控制台程序 添加包Syncfusion.Pdf.Net.Core包&#xff0c;当前官方的版本号为26.1.39 直接上代码 Syncfusion.Pdf.PdfDocument pdfDocument new Syncfusion.Pdf.PdfDocument(); for (int i 1; i < 10; i) {var page pdfDocument.Pages.Add();PdfGraphics graphics…

销量位列第一!强力巨彩LED单元板成绩斐然

据全球知名科技研究机构Omdia《LED显示产品出货分析-中国-2023》报告显示&#xff0c;2023年强力巨彩LED显示屏销量与单元板产品销量均位列第一&#xff0c;其品牌和市场优势可见一斑。 厦门强力巨彩自2004年成立之初&#xff0c;便以技术创新和严格品控为核心竞争力&#xff0…

Redis慢查询

Redis慢查询 目录 Redis慢查询慢查询配置慢日志操作返回参数介绍 Redis的慢查询就是当命令执行时间超过预定的阈值后将这条命令记录下来&#xff0c;与MySQL的功能类似 慢查询配置 默认阈值是10毫秒&#xff0c;即10000微秒 临时修改阈值为20毫秒 127.0.0.1:6379> confi…

汽车零部件材料耐候性测试氙光太阳辐射系统试验箱

概述 汽车零部件等领域的材料耐候性测试是一项关键的质量控制环节&#xff0c;它关乎汽车部件在各种气候条件下的性能表现和寿命。塑料件光照老化实验箱&#xff0c;即氙灯老化试验箱&#xff0c;在其中扮演着至关重要的角色。通过模拟自然环境中的光照、温度、湿度等条件&…

哈希表(C++实现)

文章目录 写在前面1. 哈希概念2. 哈希冲突3. 哈希函数4.哈希冲突解决4.1 闭散列4.1.1 线性探测4.1.2 采用线性探测的方式解决哈希冲突实现哈希表4.1.3 二次探测 4.2 开散列4.2.2 采用链地址法的方式解决哈希冲突实现哈希表 写在前面 在我们之前实现的所有数据结构中(比如&…

分享一个用于深入分析【大模型LLM】工作原理的工具

背景 LLM Transparency Tool 是一个用于深入分析和理解大型语言模型&#xff08;LLM&#xff09;工作原理的工具&#xff0c;旨在增加这些复杂系统的透明度。它提供了一个交互式界面&#xff0c;用户可以通过它观察、分析模型对特定输入&#xff08;prompts&#xff09;的反应…

Java案例找素数(三种方法)

目录 一&#xff1a;问题&#xff1a; 二&#xff1a;思路分析&#xff1a; 三&#xff1a;具体代码&#xff1a; 四&#xff1a;运行结果&#xff1a; 一&#xff1a;问题&#xff1a; 二&#xff1a;思路分析&#xff1a; 三&#xff1a;具体代码&#xff1a; Ⅰ&#xf…

硬件开发笔记(二十三):贴片电阻的类别、封装介绍,AD21导入贴片电阻原理图封装库3D模型

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140110514 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

【Mybatis 与 Spring】事务相关汇总

之前分享的几篇文章可以一起看&#xff0c;形成一个体系 【Mybatis】一级缓存与二级缓存源码分析与自定义二级缓存 【Spring】Spring事务相关源码分析 【Mybatis】Mybatis数据源与事务源码分析 Spring与Mybaitis融合 SpringManagedTransaction&#xff1a; org.mybatis.spri…

如何实现Action菜单

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"自定义标题栏"相关的内容&#xff0c;本章回中将介绍自定义Action菜单.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在这里提到的…

机器人控制系列教程之并联机器人简介

背景 根据其构件的连接是否构成闭环形式&#xff0c;机器人可分为串联机器人和并联机器人两种。对于串联机器人&#xff0c;其所有的构件以串联的结构形式连接起来&#xff0c;在空间组成一种开环结构&#xff0c;因而具有工作空间大&#xff0c;灵活性好等优点&#xff0c;但…

【避雷实测】宠物空气净化器怎么选?希喂、小米、安德迈谁更值得入手!

不知道家里养猫的朋友们有没有注意到&#xff0c;每逢春夏季节&#xff0c;无论是户外还是室内&#xff0c;我们的鼻子常常感到痒痒的。户外的痒感往往是因为那些飘散的杨柳絮&#xff0c;而在室内&#xff0c;这种痒感很可能是由于猫主子的毛发飘浮在空气中所引起的。 为了能…

Qt:8.QWidget属性介绍(focuspolicy属性-控件焦点、stylesheet属性-为控件设置样式)

目录 一、focuspolicy属性-控件焦点&#xff1a; 1.1focuspolicy属性介绍&#xff1a; 1.2设置焦点策略——setFocusPolicy()&#xff1a; 1.3获取控件的焦点策略——focusPolicy()&#xff1a; 二、stylesheet属性——为控件设置样式&#xff1a; 2.1 stylesheet属性介绍…

Golang-map理解

golang-map语雀笔记整理 map的底层实现hmapbmap map是如何做到O(1)的复杂度的&#xff1f;map扩容策略 师兄问题回答 map的底层实现 hmap hmap的结构体核心字段有&#xff1a;buckets 桶数组地址&#xff0c; B 定位值&#xff0c;桶的数目是2^B个&#xff0c; count 当前map的…

黑马点评下订单-小程序下单没问题但是Postman发送请求失败了,返回401

经过多方探索&#xff0c;这个✓8错误就是由于黑马点评使用了拦截器&#xff0c;我们直接发送请求是会被拦截器拦截下来的&#xff0c;我给出的解决方案是通过配置Postman解决&#xff0c;方法很简单&#xff01; 解决方案 右边的value写上Redis里面登录所用token值就可以了…

使用zdppy_api+onlyoffice word文档在线共同编辑,附完整的vue3前端代码和python后端代码

参考文档&#xff1a; https://api.onlyoffice.com/zh/editors/basic https://api.onlyoffice.com/zh/editors/coedit 基本的架构思考&#xff1a; 文档表&#xff1a;记录的是文档信息 key&#xff1a;这个key可以标识唯一的一个文档&#xff0c;可以是文档的hash值fileType…

HttpServletResponse设置headers返回,发现headers中缺少“Content-Length“和“Content-Type“两个参数。

业务中需要将用httpUtils请求返回的headers全部返回&#xff0c;塞到HttpServletResponse中&#xff0c;代码如下&#xff1a; HttpServletResponse response;// 返回headers Arrays.stream(httpResponse.getHeaders()).forEach(header -> response.setHeader(header.getNa…

MySQL的简介和安装目录

今日总结到此结束&#xff0c;拜拜&#xff01;

写代码,为什么还需要作图?

引言 古人云 &#xff1a;一图胜千言&#xff0c;闲人说&#xff1a;无图无真相。 在日常的聊天工具当中&#xff0c;无论是使用微信&#xff0c;还是钉钉。使用图片或表情包的频次越来越高&#xff0c;那是为什么呢&#xff1f;其实在互联网没有那么发达的时候&#xff0c;我…

【Linux】:命令行参数

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关Linux命令行参数的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入…