使用NDK多线程创建UI组件

介绍

本示例介绍如何使用多线程Native接口在非UI线程创建UI组件,从而优化组件创建耗时和响应时延。

效果图预览

build_on_multi_thread

使用说明

  1. 点击CreatePageOnMultiThread按钮,多线程创建UI页面;
  2. 点击CreatePageOnUIThread按钮,在UI线程创建UI页面作为对比。

实现思路

点击CreatePageOnMultiThread按钮,跳转到多线程创建的UI页面,页面内的UI组件在多线程并行创建。

  1. CAPIComponent自定义组件用于挂载通过Native接口创建的组件树。源码参考Page.ets,根据isOnUIThread的状态分别调用CreateNodeTreeOnUIThread在UI线程创建组件和CreateNodeTreeOnMultiThread在多线程创建组件。
import { NodeContent, router } from '@kit.ArkUI';
import entry from 'libentry.so';

@Component
struct CAPIComponent {
  private rootSlot = new NodeContent();
  @State isOnUIThread: boolean = false;

  aboutToAppear(): void {
    if (this.isOnUIThread) {
      // 调用Native接口在UI线程创建组件。
      entry.CreateNodeTreeOnUIThread(this.rootSlot, this.getUIContext());
    } else {
      // 调用Native接口多线程创建组件。
      entry.CreateNodeTreeOnMultiThread(this.rootSlot, this.getUIContext());
    }
  }

  aboutToDisappear(): void {
    // 释放已创建的Native组件。
    entry.DisposeNodeTree(this.rootSlot);
  }

  build() {
    Column() {
      // Native组件树挂载点。
      ContentSlot(this.rootSlot)
    }
    .width('100%')
  }
}
  1. CreateNodeTreeOnMultiThread是对ArkTs侧暴露的native接口,此接口负责多线程创建UI组件。示例中把页面中的每个卡片拆分为一个子任务,调用OH_ArkUI_PostAsyncUITask接口在非UI线程创建卡片对应的UI组件树。源码参考NodeCreator.cpp
napi_value CreateNodeTreeOnMultiThread(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value args[2] = { nullptr, nullptr };
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 获取ArkTS侧组件挂载点。
    ArkUI_NodeContentHandle contentHandle;
    int32_t result = OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
    if (result != ARKUI_ERROR_CODE_NO_ERROR) {
        OH_LOG_ERROR(LOG_APP, "OH_ArkUI_GetNodeContentFromNapiValue Failed %{public}d", result);
        return nullptr;
    }

    // 获取上下文对象指针。
    ArkUI_ContextHandle contextHandle;
    result = OH_ArkUI_GetContextFromNapiValue(env, args[1], &contextHandle);
    if (result != ARKUI_ERROR_CODE_NO_ERROR) {
        OH_LOG_ERROR(LOG_APP, "OH_ArkUI_GetContextFromNapiValue Failed %{public}d", result);
        delete contextHandle;
        return nullptr;
    }
    
    // 创建native侧组件树根节点。
    auto scrollNode = std::make_shared<ArkUIScrollNode>();
    scrollNode->SetScrollBarDisplayMode(ARKUI_SCROLL_BAR_DISPLAY_MODE_OFF);

    // 将native侧组件树根节点挂载到UI主树上。
    result = OH_ArkUI_NodeContent_AddNode(contentHandle, scrollNode->GetHandle());
    if (result != ARKUI_ERROR_CODE_NO_ERROR) {
        OH_LOG_ERROR(LOG_APP, "OH_ArkUI_NodeContent_AddNode Failed %{public}d", result);
        delete contextHandle;
        return nullptr;
    }
    // 保存native侧组件树。
    g_nodeMap[contentHandle] = scrollNode;
    
    auto columnNode = std::make_shared<ArkUIColumnNode>();
    scrollNode->AddChild(columnNode);
    
    for (int32_t i=0;i<g_cardTypeInfos.size();i++) {
        // UI线程创建子树根节点,保证scroll的子节点顺序。
        auto columnItem = std::make_shared<ArkUIColumnNode>();
        columnItem->SetMargin(NODE_MARGIN, 0, NODE_MARGIN, 0);
        columnNode->AddChild(columnItem);
        AsyncData* asyncData = new AsyncData();
        asyncData->parent = columnItem;
        asyncData->cardInfo = g_cardTypeInfos[i];
        // 在非UI线程创建组件树,创建完成后回到主线程挂载到UI主树上。
        result = OH_ArkUI_PostAsyncUITask(contextHandle, asyncData, CreateCardNodeTree, MountNodeTree);
        if (result != ARKUI_ERROR_CODE_NO_ERROR) {
            OH_LOG_ERROR(LOG_APP, "OH_ArkUI_PostAsyncUITask Failed %{public}d", result);
            delete asyncData;
        }
    }
    delete contextHandle;
    return nullptr;
}
  1. CreateCardNodeTree会在非UI线程被调用,根据卡片类型创建对应的UI组件树并设置属性。源码参考NodeCreator.cpp
void CreateCardNodeTree(void *asyncUITaskData) {
    auto asyncData = static_cast<AsyncData*>(asyncUITaskData);
    if (!asyncData) {
        return;
    }
    
    if (asyncData->cardInfo.type == "App") {
        AppCardInfo info = asyncData->cardInfo.appCardInfo;
        asyncData->child = CreateAppCard(info);
    } else if (asyncData->cardInfo.type == "Service") {
        ServiceCardInfo info = asyncData->cardInfo.serviceCardInfo;
        asyncData->child = CreateServiceCard(info);
    }
}
  1. CreateCardNodeTree执行完成后,MountNodeTree会在UI线程被调用,将子线程创建好的UI组件树挂载到UI主树上,使其可以在页面上显示出来。源码参考NodeCreator.cpp
void MountNodeTree(void *asyncUITaskData) {
    auto asyncData = static_cast<AsyncData*>(asyncUITaskData);
    if (!asyncData) {
        return;
    }
    auto parent = asyncData->parent;
    auto child = asyncData->child;
    // 把组件树挂载到UI主树上。
    parent->AddChild(child);
    delete asyncData;
}

性能对比

本示例使用了多线程native接口在非UI线程创建UI组件,减少了UI线程组件创建布局耗时,优化了页面跳转响应时延。

参考使用SmartPerf-Host分析应用性能文档,抓取trace对比分别使用UI线程和多线程创建组件时的性能。

  • 使用UI线程创建UI组件

build_on_ui_thread_trace

  • 使用多线程创建UI组件

build_on_multi_thread_trace

trace中aboutToAppear段的耗时为UI线程组件创建的耗时。 从DispatchTouchEvent开始下一帧到OnVsyncEvent结束的耗时为UI线程组件创建布局的耗时。基于上述分析生成如下性能对比表格。

UI线程创建 多线程创建 优化比例
UI线程组件创建耗时 41.3ms 5.6ms 86.4%
UI线程组件创建布局耗时 157.7ms 129.9ms 17.5%
响应时延 216.4ms 56.2ms 74.0%

工程结构&模块类型

```
|entry/src/main/cpp                  
|   |---card
|   |   |---CardCreator.cpp                         // UI卡片创建器实现类         
|   |   |---CardCreator.h                           // UI卡片创建器声明           
|   |---common                  
|   |   |---ArkUIBaseNode.h                         // NativeNode封装类,实现组件树操作
|   |   |---ArkUINode.h                             // 派生ArkUIBaseNode类,实现属性设置操作
|   |   |---NativeModule.h                          // native接口集合获取类
|   |---data                                    
|   |   |---MockData.h                              // 定义UI卡片内容数据
|   |---node                         
|   |   |---NodeCreator.cpp                         // UI组件树创建器实现
|   |   |---NodeCreator.h                           // UI组件树创建器声明
|   |   |---TypedArkUINode.h                        // 不同类型UI组件封装类
|entry/src/main/ets                  
|   |---entryablity
|   |   |---EntryAbility.ts                         // 程序入口类             
|   |---pages                                 
|   |   |---Index.ets                               // 首页
|   |   |---Page.ets                                // native组件页面
```

参考资料

接入ArkTS页面

使用SmartPerf-Host分析应用性能

相关权限

不涉及。

依赖

不涉及。

约束与限制

1.本示例仅支持标准系统上运行。

2.本示例为Stage模型,支持API20版本SDK,SDK版本号(API Version 20 Release)。

3.本示例需要使用DevEco Studio版本号(DevEco Studio 5.0.0 Release)及以上版本才可编译运行。

下载

如需单独下载本工程,执行如下命令:

git init
git config core.sparsecheckout true
echo code/UI/NdkBuildOnMultiThread/ > .git/info/sparse-checkout
git remote add origin https://gitcode.com/openharmony/applications_app_samples.git
git pull origin master