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