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