1# NativeDisplaySoloist开发指导 (C/C++) 2<!--Kit: ArkGraphics 2D--> 3<!--Subsystem: Graphics--> 4<!--Owner: @hudi33--> 5<!--Designer: @hudi33--> 6<!--Tester: @zhaoxiaoguang2--> 7<!--Adviser: @ge-yafang--> 8 9如果开发者想在独立线程中实现帧率控制的Native侧业务,可以通过DisplaySoloist来实现,如游戏、自绘制UI框架对接等场景。 10开发者可以选择多个DisplaySoloist实例共享一个线程,也可以选择每个DisplaySoloist实例独占一个线程。 11 12## 接口说明 13 14| 函数名称 | 说明 | 15| ------------------------------------------------------------ | ----------------------------------------------------- | 16| OH_DisplaySoloist* OH_DisplaySoloist_Create (bool useExclusiveThread) | 创建一个OH_DisplaySoloist实例。 | 17| OH_DisplaySoloist_Destroy (OH_DisplaySoloist * displaySoloist) | 销毁一个OH_DisplaySoloist实例。 | 18| OH_DisplaySoloist_Start (OH_DisplaySoloist * displaySoloist, OH_DisplaySoloist_FrameCallback callback, void * data ) | 设置每帧回调函数,每次VSync信号到来时启动每帧回调。 | 19| OH_DisplaySoloist_Stop (OH_DisplaySoloist * displaySoloist) | 停止请求下一次VSync信号,并停止调用回调函数callback。 | 20| OH_DisplaySoloist_SetExpectedFrameRateRange (OH_DisplaySoloist* displaySoloist, DisplaySoloist_ExpectedRateRange* range) | 设置期望帧率范围。 | 21 22详细的接口说明请参考[NativeDisplaySoloist](../reference/apis-arkgraphics2d/capi-nativedisplaysoloist.md)。 23 24## 开发示例 25 26 本范例是通过Drawing在Native侧实现图形的绘制,通过异步线程设置期望的帧率,再根据帧率进行图形的绘制并将其呈现在NativeWindow上,图形绘制部分可参考[使用Drawing实现图形绘制与显示](graphic-drawing-overview.md)。 27 28### 添加开发依赖 29 30**添加动态链接库** 31 32CMakeLists.txt中添加以下lib。 33 34```txt 35libace_napi.z.so 36libace_ndk.z.so 37libnative_window.so 38libnative_drawing.so 39libnative_display_soloist.so 40``` 41 42**头文件** 43 44```c++ 45#include <ace/xcomponent/native_interface_xcomponent.h> 46#include "napi/native_api.h" 47#include <native_display_soloist/native_display_soloist.h> 48#include <native_drawing/drawing_bitmap.h> 49#include <native_drawing/drawing_color.h> 50#include <native_drawing/drawing_canvas.h> 51#include <native_drawing/drawing_pen.h> 52#include <native_drawing/drawing_brush.h> 53#include <native_drawing/drawing_path.h> 54#include <native_window/external_window.h> 55#include <cmath> 56#include <algorithm> 57#include <stdint.h> 58#include <sys/mman.h> 59``` 60 61### 开发步骤 62 631. 定义ArkTS接口文件XComponentContext.ts,用来对接Native层。 64 ```ts 65 export default interface XComponentContext { 66 register(): void; 67 unregister(): void; 68 destroy(): void; 69 }; 70 ``` 71 722. 定义演示页面,包含两个XComponent组件。 73 74 ```ts 75 import XComponentContext from "../interface/XComponentContext"; 76 77 @Entry 78 @Component 79 struct Index { 80 private xComponentContext1: XComponentContext | undefined = undefined; 81 private xComponentContext2: XComponentContext | undefined = undefined; 82 83 build() { 84 Column() { 85 Row() { 86 XComponent({ id: 'xcomponentId30', type: XComponentType.SURFACE, libraryname: 'entry' }) 87 .onLoad((xComponentContext) => { 88 this.xComponentContext1 = xComponentContext as XComponentContext; 89 }).width('640px') 90 }.height('40%') 91 92 Row() { 93 XComponent({ id: 'xcomponentId120', type: XComponentType.SURFACE, libraryname: 'entry' }) 94 .onLoad((xComponentContext) => { 95 this.xComponentContext2 = xComponentContext as XComponentContext; 96 }).width('640px') // 64的倍数 97 }.height('40%') 98 } 99 } 100 } 101 ``` 102 1033. 在 Native C++层获取NativeXComponent。建议使用单例模式保存XComponent。此步骤需要在napi_init的过程中处理。 104 105 创建一个PluginManger单例类,用于管理NativeXComponent。 106 ```c++ 107 class PluginManager { 108 public: 109 ~PluginManager(); 110 111 static PluginManager *GetInstance(); 112 113 void SetNativeXComponent(std::string &id, OH_NativeXComponent *nativeXComponent); 114 SampleBitMap *GetRender(std::string &id); 115 void Export(napi_env env, napi_value exports); 116 private: 117 118 std::unordered_map<std::string, OH_NativeXComponent *> nativeXComponentMap_; 119 std::unordered_map<std::string, SampleXComponent *> pluginRenderMap_; 120 }; 121 ``` 122 SampleXComponent类会在后面的绘制图形中创建。 123 ```c++ 124 void PluginManager::Export(napi_env env, napi_value exports) { 125 nativeXComponentMap_.clear(); 126 pluginRenderMap_.clear(); 127 if ((env == nullptr) || (exports == nullptr)) { 128 DRAWING_LOGE("Export: env or exports is null"); 129 return; 130 } 131 132 napi_value exportInstance = nullptr; 133 if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { 134 DRAWING_LOGE("Export: napi_get_named_property fail"); 135 return; 136 } 137 138 OH_NativeXComponent *nativeXComponent = nullptr; 139 if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) { 140 DRAWING_LOGE("Export: napi_unwrap fail"); 141 return; 142 } 143 144 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 145 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 146 if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 147 DRAWING_LOGE("Export: OH_NativeXComponent_GetXComponentId fail"); 148 return; 149 } 150 151 std::string id(idStr); 152 auto context = PluginManager::GetInstance(); 153 if ((context != nullptr) && (nativeXComponent != nullptr)) { 154 context->SetNativeXComponent(id, nativeXComponent); 155 auto render = context->GetRender(id); 156 if (render != nullptr) { 157 render->RegisterCallback(nativeXComponent); 158 render->Export(env, exports); 159 } else { 160 DRAWING_LOGE("render is nullptr"); 161 } 162 } 163 } 164 ``` 165 1664. Native层配置帧率和注册回调函数。 167 168 定义每帧回调函数内容。 169 170 ```c++ 171 static void TestCallback(long long timestamp, long long targetTimestamp, void *data) 172 { 173 // ... 174 // 获取对应的XComponent 175 OH_NativeXComponent *component = nullptr; 176 component = static_cast<OH_NativeXComponent *>(data); 177 if (component == nullptr) { 178 SAMPLE_LOGE("TestCallback: component is null"); 179 return; 180 } 181 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 182 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 183 if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 184 SAMPLE_LOGE("TestCallback: Unable to get XComponent id"); 185 return; 186 } 187 188 std::string id(idStr); 189 auto render = SampleXComponent::GetInstance(id); 190 OHNativeWindow *nativeWindow = render->GetNativeWindow(); 191 uint64_t width; 192 uint64_t height; 193 // 获取XComponent的surface大小 194 int32_t xSize = OH_NativeXComponent_GetXComponentSize(component, nativeWindow, &width, &height); 195 if ((xSize == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) && (render != nullptr)) { 196 render->Prepare(); 197 render->Create(); 198 if (id == "xcomponentId30") { 199 // 30Hz绘制时,每帧移动的距离为16像素 200 render->ConstructPath(16, 16, render->defaultOffsetY); 201 } 202 if (id == "xcomponentId120") { 203 // 120Hz绘制时,每帧移动的距离为4像素 204 render->ConstructPath(4, 4, render->defaultOffsetY); 205 } 206 // ... 207 } 208 } 209 ``` 210 211 使用DisplaySoloist接口配置帧率和注册每帧回调函数。如果使用OH_DisplaySoloist_Create创建DisplaySoloist实例时传入的参数useExclusiveThread为true,则OH_DisplaySoloist_FrameCallback以独占线程方式执行,否则OH_DisplaySoloist_FrameCallback以共享线程方式执行。 212 213 > **说明:** 214 > 215 > - 实例在调用NapiRegister后,在不需要进行帧率控制时,应进行NapiUnregister操作,避免内存泄漏问题。 216 > - 在页面跳转时,应进行NapiUnregister和NapiDestroy操作,避免内存泄漏问题。 217 218 ```c++ 219 static std::unordered_map<std::string, OH_DisplaySoloist *> g_displaySync; 220 221 napi_value SampleXComponent::NapiRegister(napi_env env, napi_callback_info info) 222 { 223 // ... 224 // 获取对应的XComponent 225 napi_value thisArg; 226 if (napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr) != napi_ok) { 227 SAMPLE_LOGE("NapiRegister: napi_get_cb_info fail"); 228 return nullptr; 229 } 230 231 napi_value exportInstance; 232 if (napi_get_named_property(env, thisArg, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { 233 SAMPLE_LOGE("NapiRegister: napi_get_named_property fail"); 234 return nullptr; 235 } 236 237 OH_NativeXComponent *nativeXComponent = nullptr; 238 if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) { 239 SAMPLE_LOGE("NapiRegister: napi_unwrap fail"); 240 return nullptr; 241 } 242 243 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 244 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 245 if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 246 SAMPLE_LOGE("NapiRegister: Unable to get XComponent id"); 247 return nullptr; 248 } 249 SAMPLE_LOGI("RegisterID = %{public}s", idStr); 250 std::string id(idStr); 251 SampleXComponent *render = SampleXComponent().GetInstance(id); 252 if (render != nullptr) { 253 OH_DisplaySoloist *nativeDisplaySoloist = nullptr; 254 if (g_displaySync.find(id) == g_displaySync.end()) { 255 // 创建OH_DisplaySoloist实例 256 // true表示OH_DisplaySoloist实例独占一个线程,false则表示共享一个线程 257 g_displaySync[id] = OH_DisplaySoloist_Create(true); 258 } 259 nativeDisplaySoloist = g_displaySync[id]; 260 // 设置期望帧率范围 261 // 此结构体成员变量分别为帧率范围的最小值、最大值以及期望帧率 262 DisplaySoloist_ExpectedRateRange range; 263 if (id == "xcomponentId30") { 264 // 第一个XComponent期望帧率为30Hz 265 range = {30, 120, 30}; 266 } 267 if (id == "xcomponentId120") { 268 // 第二个XComponent期望帧率为120Hz 269 range = {30, 120, 120}; 270 } 271 OH_DisplaySoloist_SetExpectedFrameRateRange(nativeDisplaySoloist, &range); 272 // 注册回调与使能每帧回调 273 OH_DisplaySoloist_Start(nativeDisplaySoloist, TestCallback, nativeXComponent); 274 } 275 // ... 276 } 277 278 napi_value SampleXComponent::NapiUnregister(napi_env env, napi_callback_info info) 279 { 280 // ... 281 // 取消注册每帧回调 282 OH_DisplaySoloist_Stop(g_displaySync[id]); 283 // ... 284 } 285 286 napi_value SampleXComponent::NapiDestroy(napi_env env, napi_callback_info info) 287 { 288 // ... 289 // 销毁OH_DisplaySoloist实例 290 OH_DisplaySoloist_Destroy(g_displaySync[id]); 291 g_displaySync.erase(id); 292 // ... 293 } 294 295 // 实现XComponentContext.ts中ArkTS接口与C++接口的绑定和映射。 296 void SampleXComponent::Export(napi_env env, napi_value exports) { 297 if ((env == nullptr) || (exports == nullptr)) { 298 SAMPLE_LOGE("Export: env or exports is null"); 299 return; 300 } 301 napi_property_descriptor desc[] = { 302 {"register", nullptr, SampleXComponent::NapiRegister, nullptr, nullptr, nullptr, napi_default, nullptr}, 303 {"unregister", nullptr, SampleXComponent::NapiUnregister, nullptr, nullptr, nullptr, napi_default, nullptr}, 304 {"destroy", nullptr, SampleXComponent::NapiDestroy, nullptr, nullptr, nullptr, napi_default, nullptr}}; 305 306 if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { 307 SAMPLE_LOGE("Export: napi_define_properties failed"); 308 } 309 } 310 ``` 311 3125. TS层注册和取消注册每帧回调,销毁OH_DisplaySoloist实例。 313 314 ```c++ 315 // 离开页面时,取消回调注册与销毁OH_DisplaySoloist实例 316 aboutToDisappear(): void { 317 if (this.xComponentContext1) { 318 this.xComponentContext1.unregister(); 319 this.xComponentContext1.destroy(); 320 } 321 if (this.xComponentContext2) { 322 this.xComponentContext2.unregister(); 323 this.xComponentContext2.destroy(); 324 } 325 } 326 327 Row() { 328 Button('Start') 329 .id('Start') 330 .fontSize(14) 331 .fontWeight(500) 332 .margin({ bottom: 20, right: 6, left: 6 }) 333 .onClick(() => { 334 if (this.xComponentContext1) { 335 this.xComponentContext1.register(); 336 } 337 if (this.xComponentContext2) { 338 this.xComponentContext2.register(); 339 } 340 }) 341 .width('30%') 342 .height(40) 343 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 344 345 Button('Stop') 346 .id('Stop') 347 .fontSize(14) 348 .fontWeight(500) 349 .margin({ bottom: 20, left: 6 }) 350 .onClick(() => { 351 if (this.xComponentContext1) { 352 this.xComponentContext1.unregister(); 353 } 354 if (this.xComponentContext2) { 355 this.xComponentContext2.unregister(); 356 } 357 }) 358 .width('30%') 359 .height(40) 360 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 361 } 362 ``` 363 364<!--RP1--> 365## 相关实例 366 367针对可变帧率的开发,有以下相关实例可供参考: 368 369- [DisplaySoloist分级管控 (API14)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/DocsSample/graphic/DisplaySoloist) 370<!--RP1End-->