• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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-->