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