一、背景
在動態化-鴻蒙跨端方案文章中,講述了動態化適配鴻蒙的方案實現,當在鴻蒙系統進行UI渲染的時候,我們使用了系統的組件進行遞歸渲染。在iOS和Android也是借助各自系統組件進行的渲染,但是在鴻蒙系統會存在以下4個嚴重問題:
1. UI層級過多
以金融APP理財頻道頁中的一個樂高樓層中的“7天理財”文案為例,鴻蒙系統總計52層,iOS30層。層級過多會直接影響渲染性能,到達一定層級后會造成頁面掉幀和卡頓。
2. 通訊流程長
在實現鴻蒙跨端方案中,JS虛擬機(V8)運行JS代碼,通過JSI打通C++,再通過華為NAPI從C++打通ArkTS,跨語言通訊成本高。
3. 列表渲染性能差
長列表渲染性能是iOS、Android、Harmony系統非常重要的指標,華為也一直在推出多種方案以提升列表渲染性能。但在業界所有三方框架渲染長列表復雜業務場景(例如社區頻道頁面)時,在ArkUI層因設計原理導致性能問題一直無法完美解決。
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接口,流程大大縮短,數據類型轉換變少了,列表渲染過程也由接入方自主控制,并且可以做預渲染等優化方案,同時避免了系統的二次布局。
3.如何使用
在實際的動態化鴻蒙跨端中,會存在ArkTS組件和C組件嵌套的場景(對于一些對性能影響較小的組件允許使用ArkTS),下面我們實現一個比較復雜的嵌套Demo,以展示整個嵌套實現過程。包含了ArkTS組件插入C組件、ArkTS組件插入ArkTS組件、C組件插入C組件、C組件插入ArkTS組件等場景。
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(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( 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)的深度已經和其他兩端能對齊了。
3.2、其他場景實現
從上面ArkTS組件插入C組件一個過程實現能看到,代碼量還是比較驚人的,其他場景的實現讀者可以參考官方文檔進行嘗試。
審核編輯 黃宇
-
渲染
+關注
關注
0文章
74瀏覽量
11047 -
CAPI
+關注
關注
0文章
5瀏覽量
12541 -
鴻蒙系統
+關注
關注
183文章
2638瀏覽量
67273 -
鴻蒙
+關注
關注
57文章
2459瀏覽量
43462
發布評論請先 登錄
相關推薦
鴻蒙OS開發實戰:【ArkTS 實現MQTT協議(2)】

鴻蒙跨端實踐-布局方案介紹

鴻蒙ArkTS的起源和簡介
鴻蒙語言ArkTS(更好的生產力與性能)
鴻蒙Flutter實戰:06-使用ArkTs開發Flutter鴻蒙插件
鴻蒙生態-2022HDC鴻蒙應用與原子化服務全新技術呈現
全新升級的鴻蒙開發套件,你想知道的都在這里
全新升級的鴻蒙開發套件,你想知道的都在這里
HarmonyOS/OpenHarmony應用開發-ArkTS的聲明式開發范式
鴻蒙 OS 應用開發初體驗
CAPI SNAP開發及應用教程

鴻蒙跨端實踐-長列表解決方案和性能優化

評論