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)