README.md
1# 使用NDK多线程创建UI组件
2
3### 介绍
4
5本示例介绍如何使用多线程Native接口在非UI线程创建UI组件,从而优化组件创建耗时和响应时延。
6
7### 效果图预览
8
9
10
11**使用说明**
12
131. 点击CreatePageOnMultiThread按钮,多线程创建UI页面;
142. 点击CreatePageOnUIThread按钮,在UI线程创建UI页面作为对比。
15
16### 实现思路
17
18点击CreatePageOnMultiThread按钮,跳转到多线程创建的UI页面,页面内的UI组件在多线程并行创建。
19
201. CAPIComponent自定义组件用于挂载通过Native接口创建的组件树。源码参考[Page.ets](./entry/src/main/ets/pages/Page.ets),根据isOnUIThread的状态分别调用CreateNodeTreeOnUIThread在UI线程创建组件和CreateNodeTreeOnMultiThread在多线程创建组件。
21
22```ts
23import { NodeContent, router } from '@kit.ArkUI';
24import entry from 'libentry.so';
25
26@Component
27struct CAPIComponent {
28 private rootSlot = new NodeContent();
29 @State isOnUIThread: boolean = false;
30
31 aboutToAppear(): void {
32 if (this.isOnUIThread) {
33 // 调用Native接口在UI线程创建组件。
34 entry.CreateNodeTreeOnUIThread(this.rootSlot, this.getUIContext());
35 } else {
36 // 调用Native接口多线程创建组件。
37 entry.CreateNodeTreeOnMultiThread(this.rootSlot, this.getUIContext());
38 }
39 }
40
41 aboutToDisappear(): void {
42 // 释放已创建的Native组件。
43 entry.DisposeNodeTree(this.rootSlot);
44 }
45
46 build() {
47 Column() {
48 // Native组件树挂载点。
49 ContentSlot(this.rootSlot)
50 }
51 .width('100%')
52 }
53}
54```
55
562. CreateNodeTreeOnMultiThread是对ArkTs侧暴露的native接口,此接口负责多线程创建UI组件。示例中把页面中的每个卡片拆分为一个子任务,调用OH_ArkUI_PostAsyncUITask接口在非UI线程创建卡片对应的UI组件树。源码参考[NodeCreator.cpp](./entry/src/main/cpp/node/NodeCreator.cpp)
57
58```cpp
59napi_value CreateNodeTreeOnMultiThread(napi_env env, napi_callback_info info) {
60 size_t argc = 2;
61 napi_value args[2] = { nullptr, nullptr };
62 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
63
64 // 获取ArkTs侧组件挂载点。
65 ArkUI_NodeContentHandle contentHandle;
66 int32_t result = OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
67 if (result != ARKUI_ERROR_CODE_NO_ERROR) {
68 OH_LOG_ERROR(LOG_APP, "OH_ArkUI_GetNodeContentFromNapiValue Failed %{public}d", result);
69 return nullptr;
70 }
71
72 // 获取上下文对象指针。
73 ArkUI_ContextHandle contextHandle;
74 result = OH_ArkUI_GetContextFromNapiValue(env, args[1], &contextHandle);
75 if (result != ARKUI_ERROR_CODE_NO_ERROR) {
76 OH_LOG_ERROR(LOG_APP, "OH_ArkUI_GetContextFromNapiValue Failed %{public}d", result);
77 delete contextHandle;
78 return nullptr;
79 }
80
81 // 创建native侧组件树根节点。
82 auto scrollNode = std::make_shared<ArkUIScrollNode>();
83 scrollNode->SetScrollBarDisplayMode(ARKUI_SCROLL_BAR_DISPLAY_MODE_OFF);
84
85 // 将native侧组件树根节点挂载到UI主树上。
86 result = OH_ArkUI_NodeContent_AddNode(contentHandle, scrollNode->GetHandle());
87 if (result != ARKUI_ERROR_CODE_NO_ERROR) {
88 OH_LOG_ERROR(LOG_APP, "OH_ArkUI_NodeContent_AddNode Failed %{public}d", result);
89 delete contextHandle;
90 return nullptr;
91 }
92 // 保存native侧组件树。
93 g_nodeMap[contentHandle] = scrollNode;
94
95 auto columnNode = std::make_shared<ArkUIColumnNode>();
96 scrollNode->AddChild(columnNode);
97
98 for (int32_t i=0;i<g_cardTypeInfos.size();i++) {
99 // UI线程创建子树根节点,保证scroll的子节点顺序。
100 auto columnItem = std::make_shared<ArkUIColumnNode>();
101 columnItem->SetMargin(NODE_MARGIN, 0, NODE_MARGIN, 0);
102 columnNode->AddChild(columnItem);
103 AsyncData* asyncData = new AsyncData();
104 asyncData->parent = columnItem;
105 asyncData->cardInfo = g_cardTypeInfos[i];
106 // 在非UI线程创建组件树,创建完成后回到主线程挂载到UI主树上。
107 result = OH_ArkUI_PostAsyncUITask(contextHandle, asyncData, CreateCardNodeTree, MountNodeTree);
108 if (result != ARKUI_ERROR_CODE_NO_ERROR) {
109 OH_LOG_ERROR(LOG_APP, "OH_ArkUI_PostAsyncUITask Failed %{public}d", result);
110 delete asyncData;
111 }
112 }
113 delete contextHandle;
114 return nullptr;
115}
116```
117
1183. CreateCardNodeTree会在非UI线程被调用,根据卡片类型创建对应的UI组件树并设置属性。源码参考[NodeCreator.cpp](./entry/src/main/cpp/node/NodeCreator.cpp)
119
120```cpp
121void CreateCardNodeTree(void *asyncUITaskData) {
122 auto asyncData = static_cast<AsyncData*>(asyncUITaskData);
123 if (!asyncData) {
124 return;
125 }
126
127 if (asyncData->cardInfo.type == "App") {
128 AppCardInfo info = asyncData->cardInfo.appCardInfo;
129 asyncData->child = CreateAppCard(info);
130 } else if (asyncData->cardInfo.type == "Service") {
131 ServiceCardInfo info = asyncData->cardInfo.serviceCardInfo;
132 asyncData->child = CreateServiceCard(info);
133 }
134}
135```
136
1374. CreateCardNodeTree执行完成后,MountNodeTree会在UI线程被调用,将子线程创建好的UI组件树挂载到UI主树上,使其可以在页面上显示出来。源码参考[NodeCreator.cpp](./entry/src/main/cpp/node/NodeCreator.cpp)
138
139```cpp
140void MountNodeTree(void *asyncUITaskData) {
141 auto asyncData = static_cast<AsyncData*>(asyncUITaskData);
142 if (!asyncData) {
143 return;
144 }
145 auto parent = asyncData->parent;
146 auto child = asyncData->child;
147 // 把组件树挂载到UI主树上。
148 parent->AddChild(child);
149 delete asyncData;
150}
151```
152
153### 性能对比
154
155本示例使用了多线程native接口在非UI线程创建UI组件,减少了UI线程组件创建布局耗时,优化了页面跳转响应时延。
156
157- 使用UI线程创建UI组件
158
159
160
161- 使用多线程创建UI组件
162
163
164
165| | UI线程创建 | 多线程创建 | 优化比例 |
166| -------- | -------- | -------- | -------- |
167| UI线程组件创建耗时 | 41.3ms | 5.6ms | 86.4% |
168| UI线程组件创建布局耗时 | 157.7ms | 129.9ms | 17.5% |
169| 响应时延 | 216.4ms | 56.2ms | 74.0% |
170
171### 工程结构&模块类型
172
173 ```
174 |entry/src/main/cpp
175 | |---card
176 | | |---CardCreator.cpp // UI卡片创建器实现类
177 | | |---CardCreator.h // UI卡片创建器声明
178 | |---common
179 | | |---ArkUIBaseNode.h // NativeNode封装类,实现组件树操作
180 | | |---ArkUINode.h // 派生ArkUIBaseNode类,实现属性设置操作
181 | | |---NativeModule.h // native接口集合获取类
182 | |---data
183 | | |---MockData.h // 定义UI卡片内容数据
184 | |---node
185 | | |---NodeCreator.cpp // UI组件树创建器实现
186 | | |---NodeCreator.h // UI组件树创建器声明
187 | | |---TypedArkUINode.h // 不同类型UI组件封装类
188 |entry/src/main/ets
189 | |---entryablity
190 | | |---EntryAbility.ts // 程序入口类
191 | |---pages
192 | | |---Index.ets // 首页
193 | | |---Page.ets // native组件页面
194 ```
195
196### 参考资料
197
198[接入ArkTS页面](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/ui/ndk-access-the-arkts-page.md)
199
200### 相关权限
201
202不涉及。
203
204### 依赖
205
206不涉及。
207
208### 约束与限制
209
2101.本示例仅支持标准系统上运行。
211
2122.本示例为Stage模型,支持API20版本SDK,SDK版本号(API Version 20 Release)。
213
2143.本示例需要使用DevEco Studio版本号(DevEco Studio 5.0.0 Release)及以上版本才可编译运行。
215
216### 下载
217
218如需单独下载本工程,执行如下命令:
219
220```shell
221git init
222git config core.sparsecheckout true
223echo code/UI/NdkBuildOnMultiThread/ > .git/info/sparse-checkout
224git remote add origin https://gitcode.com/openharmony/applications_app_samples.git
225git pull origin master