0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙跨端实践-ArkTS和CAPI的混合开发实现

京东云 来源:jf_75140285 作者:jf_75140285 2024-09-02 10:18 次阅读

一、背景

在动态化-鸿蒙跨端方案文章中,讲述了动态化适配鸿蒙的方案实现,当在鸿蒙系统进行UI渲染的时候,我们使用了系统的组件进行递归渲染。在iOSAndroid也是借助各自系统组件进行的渲染,但是在鸿蒙系统会存在以下4个严重问题:

1. UI层级过多

以金融APP理财频道页中的一个乐高楼层中的“7天理财”文案为例,鸿蒙系统总计52层,iOS30层。层级过多会直接影响渲染性能,到达一定层级后会造成页面掉帧和卡顿。

wKgZombVIH-AbkqAAATXo0dV640550.png

2. 通讯流程长

在实现鸿蒙跨端方案中,JS虚拟机(V8)运行JS代码,通过JSI打通C++,再通过华为NAPI从C++打通ArkTS,跨语言通讯成本高。

wKgaombVIIGAA9j2AAEaQOLpxvw524.png

3. 列表渲染性能差

长列表渲染性能是iOS、Android、Harmony系统非常重要的指标,华为也一直在推出多种方案以提升列表渲染性能。但在业界所有三方框架渲染长列表复杂业务场景(例如社区频道页面)时,在ArkUI层因设计原理导致性能问题一直无法完美解决。

wKgZombVIIOAQgsEAAXIOCEXR0w854.png

4、二次布局

在对接到鸿蒙系统组件后,因为设置了相关布局属性后,系统会进行二次布局。

二、新方案实践

1.问题剖析

UI层级过多:原因在于在鸿蒙系统使用系统组件进行递归渲染的时候,需要借助自定义组件进行实现,然而和iOS和Android端的命令式组件渲染不同,比如RomaDiv对应iOS就是直接翻译为UIView即可,在鸿蒙必须增加一个包裹的容器才是一个合法的自定义组件,比如Stack容器,这样每个组件的层级就多了一层。

@Componentexport 
struct RomaDiv {
    build(){
        Stack(){
            //借助wrapBuilder实现递归
            ForEach(this.childrenTags, (childrenTag) => {
                  RomaComponentFactory.builder()//RomaComponentFactory就是对应鸿蒙系统提供的WrappedBuilder
            })
        }
    }
}

通讯流程长:js代码运行在系统内置的V8虚拟机中,ArkTS代码运行在华为的方舟虚拟机中,再加上V8运行js的线程,C++解析js指令的线程以及ArkTS的主线程,跨线程开销耗时增加,以及各个语言间的数据类型转换,通讯成本必然会非常高。

列表渲染性能差:鸿蒙的响应式编程,底层类似于vue做了依赖收集,虽然长列表场景下华为提供了cacheCount机制以提升列表渲染性能,但当数据发生变化的时候,数据的递归分析以及不在屏幕的的节点属性设置直接导致了列表性能的大幅下降。

二次布局:动态化在鸿蒙系统的跨端已经集成了另外两端共同使用的Yoga布局库,其实在给华为系统组件设置属性和坐标之前已经做好了布局计算,但是华为系统并未感知和处理这个过程,所以会存在二次布局的问题。

2.新方案简介

针对以上问题,通过和华为沟通,鸿蒙系统提供了C语言的命令式接口。C组件接口是介于UI组件的Native实现和ArkTS对接层之间的一层C接口封装,它绕过了状态管理对组件变化、刷新的自动化管理,同时避免了JS引擎和C++之间类型转换和跨语言调用的开销,因此具有较好的性能。

通过C接口的对接,UI层级能直接和另外两端基本一致,通讯过程直接从JS到C++,C++可以直接调用C接口,流程大大缩短,数据类型转换变少了,列表渲染过程也由接入方自主控制,并且可以做预渲染等优化方案,同时避免了系统的二次布局。

wKgaombVIIOAOgw4AAB_uSC4Mg8553.png

3.如何使用

在实际的动态化鸿蒙跨端中,会存在ArkTS组件和C组件嵌套的场景(对于一些对性能影响较小的组件允许使用ArkTS),下面我们实现一个比较复杂的嵌套Demo,以展示整个嵌套实现过程。包含了ArkTS组件插入C组件、ArkTS组件插入ArkTS组件、C组件插入C组件、C组件插入ArkTS组件等场景。

wKgZombVIIWACw25AAJtauw4czg471.png

3.1、ArkTS插入C组件示例

ArkTS组件插入C组件的主要过程分为三步:

1、NodeContent管理器创建

2、build函数中的ContentSlot占位组件

3、NodeContent节点创建(CAPI)

import entry from 'libentry.so'; 
import { NodeContent } from '@ohos.arkui.node'

@Entry
@Component
struct CMixArkTS{ 
     //1、NodeContent管理器创建
     private divNodeContent: NodeContent = new NodeContent();
 }

build(){
    //2、build函数中的ContentSlot占位组件
    ContentSlot(this.divNodeContent);
}

aboutToAppear(): void {
    //3、NodeContent节点创建(CAPI)
    entry.CreateNativeDivNode(this.divNodeContent);
}

CreateNativeDivNode在C++中的实现如下:

此处有个坑:ArkUI_NativeNodeAPI_1 *nodeAPI 如果按照官方文档代码创建会失败,正确的方法如下代码所示。因为使用到ArkUI_NativeNodeAPI_1的地方比较多,所以我把ArkUI_NativeNodeAPI_1封装到CAPIManager::getNodeAPI()方法中了。

这个过程的核心API为OH_ArkUI_NodeContent_AddNode(nodeContentHandle_, DivComponent); 第一个参数指向ArkTS侧传入的nodeContent,第二个参数就是使用CAPI创建的Div节点。

// 1、C组件-绿色边框
static napi_value CreateNativeDivNode(napi_env env, napi_callback_info info) {
    // napi相关处理空指针&数据越界等问题
    if ((env == nullptr) || (info == nullptr)) {
        return nullptr;
    }

    napi_value returnVal = nullptr;

    size_t argc = 1;
    napi_value args[1] = {nullptr};
    if (napi_get_cb_info(env, info, &argc, args, nullptr, nullptr) != napi_ok) {
        OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "napi_init", "CreateNativeNode napi_get_cb_info failed");
    }

    if (argc != 1) {
        return nullptr;
    }
    // 将nodeContentHandle_指向ArkTS侧传入的nodeContent
    // 在Native侧获取ArkTS侧Content指针。
    OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &nodeContentHandle_);

    // nodeAPI = reinterpret_cast< ArkUI_NativeNodeAPI_1 * >(OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE,
    // "ArkUI_NativeNode_API_1")); 上面写法不行,必须如下写法......
    // ArkUI_NativeNodeAPI_1 声明 ArkUI 提供的原生节点 API 集合。 与原生节点相关的 API 必须在主线程中调用。
    // 包括创建节点、添加、删除节点,给节点设置各种属性样式等
    static ArkUI_NativeNodeAPI_1 *nodeAPI = nullptr;
    if (nodeAPI == nullptr) {
        nodeAPI = reinterpret_cast< ArkUI_NativeNodeAPI_1 * >(
            OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"));
    }

    if (nodeAPI != nullptr) {
        if (nodeAPI->createNode != nullptr && nodeAPI->addChild != nullptr) {
            ArkUI_NodeHandle DivComponent;
            // 创建div节点
            DivComponent = CreateDivNodeHandle();
            // nodeContentHandle_指向ArkTS侧传入的nodeContent,nodeContent上div节点
            OH_ArkUI_NodeContent_AddNode(nodeContentHandle_, DivComponent);
        }
    }

    return returnVal;
}

static ArkUI_NodeHandle CreateDivNodeHandle() {
    ArkUI_NodeHandle greenDivNodeHandle;
    // 创建div的node
    greenDivNodeHandle = CreateDivNodeHandleWithParam(200, 0xFF00FF00);
    CAPIManager::GetInstance()->greenDivNodeHandle = greenDivNodeHandle;
    return greenDivNodeHandle;
}

static napi_value Init(napi_env env, napi_value exports){
    { "CreateNativeDivNode", nullptr, CreateNativeDivNode, nullptr, nullptr, nullptr, napi_default, nullptr},
}

真正的C组件创建:

static ArkUI_NodeHandle CreateDivNodeHandleWithParam(float height, uint32_t borderColor) {
 ArkUI_NodeHandle divNode = CAPIManager::getNodeAPI()->createNode(ArkUI_NodeType::ARKUI_NODE_FLEX);
 // margin
 ArkUI_NumberValue number = {.f32 = 5};
 ArkUI_AttributeItem marginValue = {
 .value = &number, // 初始化为NULL或者指向你的数字数组
 .size = 1, // 初始化为你的数字数组的大小
 .string = NULL, // 初始化为NULL或者指向你的字符串
 .object = NULL // 初始化为NULL或者指向你的对象
 };

 // borderWidth
 ArkUI_NumberValue number2 = {.f32 = 2};
 ArkUI_AttributeItem borderWValue = {
 .value = &number2, // 初始化为NULL或者指向你的数字数组
 .size = 1, // 初始化为你的数字数组的大小
 .string = NULL, // 初始化为NULL或者指向你的字符串
 .object = NULL // 初始化为NULL或者指向你的对象
 };

 // 背景色
 ArkUI_NumberValue number1 = {.u32 = borderColor};
 ArkUI_AttributeItem borderColorItem = {
 .value = &number1, // 初始化为NULL或者指向你的数字数组
 .size = 1, // 初始化为你的数字数组的大小
 .string = NULL, // 初始化为NULL或者指向你的字符串
 .object = NULL // 初始化为NULL或者指向你的对象
 };

 // 宽高
 ArkUI_NumberValue number3 = {.f32 = height};
 ArkUI_AttributeItem hValue = {
 .value = &number3, // 初始化为NULL或者指向你的数字数组
 .size = 1, // 初始化为你的数字数组的大小
 .string = NULL, // 初始化为NULL或者指向你的字符串
 .object = NULL // 初始化为NULL或者指向你的对象
 };
 ArkUI_NumberValue number5 = {.f32 = 0.9};
 ArkUI_AttributeItem wValue = {
 .value = &number5, // 初始化为NULL或者指向你的数字数组
 .size = 1, // 初始化为你的数字数组的大小
 .string = NULL, // 初始化为NULL或者指向你的字符串
 .object = NULL // 初始化为NULL或者指向你的对象
 };

 ArkUI_NumberValue number4 = {.i32 = ARKUI_ITEM_ALIGNMENT_CENTER};
 ArkUI_AttributeItem alignment = {
 .value = &number4, // 初始化为NULL或者指向你的数字数组
 .size = 1, // 初始化为你的数字数组的大小
 .string = NULL, // 初始化为NULL或者指向你的字符串
 .object = NULL // 初始化为NULL或者指向你的对象
 };
 // 属性设置

 CAPIManager::getNodeAPI()->setAttribute(divNode, NODE_MARGIN, &marginValue);
 CAPIManager::getNodeAPI()->setAttribute(divNode, NODE_BORDER_WIDTH, &borderWValue);
 CAPIManager::getNodeAPI()->setAttribute(divNode, NODE_BORDER_COLOR, &borderColorItem);
 CAPIManager::getNodeAPI()->setAttribute(divNode, NODE_WIDTH_PERCENT, &wValue);
 CAPIManager::getNodeAPI()->setAttribute(divNode, NODE_HEIGHT, &hValue);
 CAPIManager::getNodeAPI()->setAttribute(divNode, NODE_ALIGN_SELF, &alignment);

 return divNode;
}

通过以上过程可以发现,通过CAPI创建一个节点并渲染的过程还是比较复杂的,但只要抓住实现过程的核心步骤,剩下的就是按照文档开发就行了。

大家感受下iOS实现这个过程的模拟

- (UIView *) CreateNativeDivNode{
    UIView* div = [UIView new]; 
    div.backGroundColor = [UIColor greenColor];
    div.frame = CGRectMake(0,0,width,height);
    return div;
}

虽然过程有点复杂,但是效果还是不错的,毕竟能解决文章开头提出的4个问题。比如最直观的UI层级,Text26(I am A ArkTS Node)的深度已经和其他两端能对齐了。

wKgaombVIIWAehh9AAD7gAHUIv4803.png

3.2、其他场景实现

从上面ArkTS组件插入C组件一个过程实现能看到,代码量还是比较惊人的,其他场景的实现读者可以参考官方文档进行尝试。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 渲染
    +关注

    关注

    0

    文章

    68

    浏览量

    10873
  • CAPI
    +关注

    关注

    0

    文章

    5

    浏览量

    12475
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2627

    浏览量

    65786
  • 鸿蒙
    +关注

    关注

    56

    文章

    2267

    浏览量

    42489
收藏 人收藏

    评论

    相关推荐

    鸿蒙OS开发实战:【ArkTS 实现MQTT协议(2)】

    1. 协议传输通道仅为TCPSocket 2. 基于HarmonyOS SDK API 9开发 3. 开发语言:ArkTS,TypeScript
    的头像 发表于 04-01 14:48 1304次阅读
    <b class='flag-5'>鸿蒙</b>OS<b class='flag-5'>开发</b>实战:【<b class='flag-5'>ArkTS</b> <b class='flag-5'>实现</b>MQTT协议(2)】

    鸿蒙实践-布局方案介绍

    封装到标签中实现,业务只需要针对标签简单地设置相关属性,即可实现列表类布局,大幅提升研发效率。同时动态化也支持绝对布局以及控制视图的显示和隐藏等功能,使之能胜任绝大多数业务布局场景。 在京东金融App使用动态化方案适配鸿蒙系统的
    的头像 发表于 09-18 10:26 590次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>跨</b><b class='flag-5'>端</b><b class='flag-5'>实践</b>-布局方案介绍

    鸿蒙ArkTS的起源和简介

    ArkTS也会结合应用开发及运行的其他方面需求持续演进: 更完善的类型系统 我们已经设计并实现了专门运行时,利用ArkTS的类型输入,在程序执行一开始就获得较高的运行性能(不像其它传
    发表于 01-16 16:23

    鸿蒙语言ArkTS(更好的生产力与性能)

    ArkTS鸿蒙生态的应用开发语言 ArkTS提供了声明式UI范式、状态管理支持等相应的能力,让开发者可以以更简洁、更自然的方式
    发表于 02-17 15:56

    鸿蒙原生应用元服务开发-仓颉ArkTS相互操作(一)

    在 OpenHarmony 系统上,ArkTS 具备完整广泛的生态,为复用 ArkTS 生态,仓颉支持与 ArkTS 高效语言互通。 仓颉-Ark
    发表于 07-31 17:43

    如何理解鸿蒙OS是设备的?

    谁能帮忙解释鸿蒙OS是怎样实现平台的?
    发表于 09-08 18:17

    鸿蒙生态-2022HDC鸿蒙应用与原子化服务全新技术呈现

    基础的ArkTS语言介绍到高阶分布式设备应用开发等一系列官方视频课程,以及配套的开发文档、SDK、API、示范代码案例等。开发者通过学习本
    发表于 11-02 16:32

    全新升级的鸿蒙开发套件,你想知道的都在这里

    ArkTS应用一键上架分发,服务于HarmonyOS生态的全生命周期,开发完成后支持一键应用上传发布到多终端。 本次华为还发布了可体验鸿蒙
    发表于 11-04 18:47

    全新升级的鸿蒙开发套件,你想知道的都在这里

    、控件对象Dom树、控件属性等多项能力。AppGallery Connect 实现ArkTS应用一键上架分发,服务于HarmonyOS生态的全生命周期,
    发表于 11-07 17:22

    HarmonyOS/OpenHarmony应用开发-ArkTS的声明式开发范式

    基于ArkTS的声明式开发范式的方舟开发框架是一套开发极简、高性能、设备应用的UI开发框架,支
    发表于 01-17 15:09

    HarmonyOS优选主力应用开发语言-ArkTS概述

    父子组件之间、爷孙组件之间,还可以在应用全局范围内传递或设备传递。另外,从数据的传递形式来看,可分为只读的单向传递和可变更的双向传递。开发者可以灵活的利用这些能力来实现数据和UI的联动。 渲染控制
    发表于 06-09 10:52

    OpenHarmony 应用开发SDK、API 与基础工具

    ArkTS 对象的能力,使用类 Node 的 N-API 接口命名。开发者使用 C/C++开发业务,通过 N-API 接口实现语言调用,
    发表于 09-19 15:45

    鸿蒙 OS 应用开发初体验

    的操作系统平台和开发框架。HarmonyOS 的目标是实现设备的无缝协同和高性能。 DevEco Studio 对标 Android Studio,开发
    发表于 11-02 19:38

    CAPI SNAP开发及应用教程

    在之前的OpenPOWER欧洲峰会上,我们推出了全新的框架,旨在便于开发者开始采用CAPI加速其应用开发CAPI存储、网络和分析编程框架,或者简称为
    发表于 11-16 13:16 1799次阅读
    <b class='flag-5'>CAPI</b> SNAP<b class='flag-5'>开发</b>及应用教程

    鸿蒙开发ArkTS基础知识

    更简洁、更自然的方式开发应用。了解ArkTS之前,我们需要先了解下ArkTS、TypeScript和JavaScript之间的关系。 J
    的头像 发表于 01-24 16:44 1783次阅读
    <b class='flag-5'>鸿蒙</b><b class='flag-5'>开发</b>之<b class='flag-5'>ArkTS</b>基础知识