自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

RN框架在攜程旅行鴻蒙應(yīng)用的全業(yè)務(wù)適配實(shí)踐

開發(fā) 移動(dòng)開發(fā)
攜程作為鴻蒙生態(tài)在旅游行業(yè)的重要合作伙伴,早在鴻蒙服務(wù)卡片時(shí)期就和華為開始合作。2023年9月,華為宣布鴻蒙原生應(yīng)用啟動(dòng)開發(fā),同年12月,我們完成攜程旅行鴻蒙Beta版本的開發(fā),技術(shù)上基于Web+部分原生的方案實(shí)現(xiàn)。24年6月HarmonyOS Next系統(tǒng)正式內(nèi)測(cè)后,為了讓鴻蒙生態(tài)的用戶使用到攜程一站式的旅行服務(wù),我們開始在鴻蒙系統(tǒng)上對(duì)全業(yè)務(wù)進(jìn)行適配。

一、RN在攜程業(yè)務(wù)使用現(xiàn)狀

2019年,攜程開始在線上使用RN框架,并結(jié)合自身的業(yè)場(chǎng)景,對(duì)RN框架進(jìn)行了開發(fā)和改造,研發(fā)了CRN框架(以下簡(jiǎn)稱CRN)。2021年,CRN成為攜程主流的開發(fā)框架。集團(tuán)內(nèi)有20+個(gè)App接入CRN框架,其中核心的App都已接入。攜程旅行App中,200+個(gè)業(yè)務(wù)Bundle在線上運(yùn)行,業(yè)務(wù)頁(yè)面數(shù)量超過(guò)2000個(gè),超過(guò)80%的業(yè)務(wù)使用CRN。

二、技術(shù)選型(為什么選擇CRN)

從新技術(shù)的選擇到落地的實(shí)踐上看,業(yè)務(wù)對(duì)技術(shù)的要求往往是以下幾個(gè)方面:

1)功能全,全量業(yè)務(wù)都能快速的適配上線

2)性能好,用戶體驗(yàn)多端一致

3)成本低,復(fù)用現(xiàn)有在其他平臺(tái)的運(yùn)行的代碼

為了滿足業(yè)務(wù)需求,鴻蒙的實(shí)現(xiàn)技術(shù)上我們選擇了CRN,主要考慮:

1)基建成熟度高:有配套研發(fā)/測(cè)試/發(fā)布/運(yùn)營(yíng)監(jiān)控系統(tǒng),內(nèi)部交流活躍,知識(shí)沉淀深

2)業(yè)務(wù)適配成本?。簶I(yè)務(wù)不需要重新再開發(fā)一遍,可以使用現(xiàn)有的業(yè)務(wù)代碼

3)開發(fā)能快速上手:業(yè)務(wù)開發(fā)還是使用原有的技術(shù)進(jìn)行開發(fā),在鴻蒙上運(yùn)行

4)產(chǎn)品迭代效率:支持每個(gè)周期的產(chǎn)品迭代,快速在鴻蒙系統(tǒng)的手機(jī)上線

三、CRN適配實(shí)踐

3.1 版本升級(jí)

線上攜程旅行App使用的React Native(RN)版本是0.70.1,而鴻蒙RN版本是0.72.5。因此,適配鴻蒙的第一步是將RN版本從0.70.1升級(jí)到0.72.5。

版本升級(jí)包含了如下幾個(gè)方面:

3.1.1 RN版本差異分析

我們對(duì)比RN 0.70.1 和 0.72.5 框架庫(kù)的差異,整體改動(dòng)點(diǎn)不多。為了降低業(yè)務(wù)方升級(jí)成本,我們?cè)诳蚣艿讓訉?duì)廢棄的組件和API變更做了兼容,盡可能減少業(yè)務(wù)使用方的改動(dòng)。

3.1.2 CRN框架改造

CRN框架覆蓋了文檔、工具、開發(fā)框架、發(fā)布、監(jiān)控、排障全鏈路。對(duì)應(yīng)框架的改造也從這幾個(gè)方面進(jìn)行。

1)在文檔方面,我們編寫了詳細(xì)的業(yè)務(wù)升級(jí)文檔,列出業(yè)務(wù)方需要關(guān)注的點(diǎn)和常見問(wèn)題。

2)在工具方面,提供了一鍵式CLI升級(jí)工具,只需在業(yè)務(wù)工程執(zhí)行一行升級(jí)命令,即可完成工程升級(jí)改造。

3)在開發(fā)框架方面,改造涉及點(diǎn)比較多,包括:

  • 對(duì)Native運(yùn)行時(shí)升級(jí),升級(jí)RN 0.72.5 核心庫(kù),合并對(duì)官方RN庫(kù)的自定義改動(dòng)點(diǎn)。
  • 對(duì)JS打包工具升級(jí),支持現(xiàn)有的拆包邏輯,合并對(duì)官方RN庫(kù)的自定義改動(dòng)點(diǎn)。
  • 梳理使用到的社區(qū)三方庫(kù),統(tǒng)一三方庫(kù)版本升級(jí)至鴻蒙RN三方庫(kù)要求版本。
  • 對(duì)Hermes引擎進(jìn)行升級(jí),合并自定義改動(dòng)點(diǎn)。
  • 對(duì)RN自定義組件和API進(jìn)行新架構(gòu)改造。

4)在發(fā)布方面,對(duì)現(xiàn)有的CRN發(fā)布系統(tǒng)進(jìn)行改造,支持選擇鴻蒙平臺(tái)進(jìn)行單獨(dú)發(fā)布。發(fā)布的產(chǎn)物下發(fā)和線上IOS/Android進(jìn)行隔離,保證測(cè)試上線階段,不影響已經(jīng)上架的IOS/Android應(yīng)用。

待后續(xù)鴻蒙應(yīng)用穩(wěn)定,再支持一鍵同時(shí)發(fā)布IOS/Android/HarmonyOS Next平臺(tái)。

又考慮到業(yè)務(wù)場(chǎng)景存在一套代碼,跨RN版本發(fā)布。發(fā)布系統(tǒng)改造,支持了發(fā)布時(shí)根據(jù)發(fā)布單選擇的RN版本,自動(dòng)選擇依賴配置進(jìn)行打包發(fā)布。提升業(yè)務(wù)發(fā)布效率。

5)在監(jiān)控方面,實(shí)現(xiàn)鴻蒙端的監(jiān)控?cái)?shù)據(jù)上報(bào),接入到現(xiàn)有的監(jiān)控系統(tǒng),方便線上監(jiān)控。

6)在排障方面,實(shí)現(xiàn)鴻蒙端的異常數(shù)據(jù)上報(bào),接入現(xiàn)有排障系統(tǒng),方便線上排障。

3.1.3 業(yè)務(wù)工程改造

1)業(yè)務(wù)方按照提供升級(jí)文檔和工具進(jìn)行具體業(yè)務(wù)工程改造。

2)升級(jí)改造后,進(jìn)行本地開發(fā)環(huán)境測(cè)試,發(fā)現(xiàn)問(wèn)題,解決問(wèn)題。

3)本地測(cè)試通過(guò)后,進(jìn)行打包發(fā)布,進(jìn)入集成測(cè)試階段。

在升級(jí)過(guò)程中,工作量最大的部分是“RN自定義組件和API實(shí)現(xiàn)新架構(gòu)改造”。

這里先介紹下RN新架構(gòu)。RN新架構(gòu)是指從0.68版本開始后的架構(gòu)。主要包括:

  • Turo Modules 模塊系統(tǒng),替換老架構(gòu)中的Native Modules,用于JS到Native的API同步調(diào)用。
  • Farbic 組件系統(tǒng),替換老架構(gòu)中Native Component,支持同步渲染。

由于鴻蒙RN只支持新架構(gòu),所以需要將RN自定義組件和API實(shí)現(xiàn)進(jìn)行新架構(gòu)改造。在攜程旅行App中,我們使用有100+的自定義組件和API。這部分的改造工作量非常大,建議在做適配時(shí)優(yōu)先處理這部分工作。

3.2 差異化工作

在RN版本升級(jí)到0.72.5后,開始鴻蒙端特有的適配。

鴻蒙RN框架特點(diǎn):

  • 已實(shí)現(xiàn)了官方RN大部分組件、API
  • 已實(shí)現(xiàn)社區(qū)常用的三方庫(kù)
  • 自定義組件和API需要應(yīng)用開發(fā)自行實(shí)現(xiàn)

差異化工作:

1)自定義組件和API實(shí)現(xiàn)

  • 100+自定義組件和API,基于鴻蒙原生開發(fā)實(shí)現(xiàn),再封裝提供給RN調(diào)用
  • 按優(yōu)先級(jí)分階段實(shí)現(xiàn)這些自定義組件和API,保持上層JS接口不變

2)RN工程改造

  • 添加react-native-harmony和react-native-harmony-cli依賴庫(kù)
  • 適配Platform.OS,Platform.select等API
  • 實(shí)現(xiàn)xxx.harmony.js文件,邏輯與IOS保持一致
  • 升級(jí)三方庫(kù)版本,如react-native-gesture-handler,從1.X版本升級(jí)到2.X版本
  • 三方庫(kù)版本升級(jí)后,對(duì)不兼容的地方做適配

3.3 原生組件開發(fā)

攜程CRN框架經(jīng)過(guò)近8年的迭代,業(yè)務(wù)線非常復(fù)雜,自定義的組件、turboModule有100多個(gè)。

在鴻蒙中適配CRN,首先面臨的工作就是將這些自定義組件、turboModule在鴻蒙原生端用ArkTS重新實(shí)現(xiàn)。

我們面臨以下幾個(gè)挑戰(zhàn):

1)工作量

這些組件經(jīng)過(guò)了近8年的迭代,開發(fā)負(fù)責(zé)人可能幾經(jīng)易手。有些復(fù)雜組件,如信息流組件、自定義地圖、日歷組件、多媒體組件等,邏輯異常復(fù)雜,經(jīng)過(guò)跟原開發(fā)負(fù)責(zé)人、產(chǎn)品等初步討論,工作量都超過(guò)單人一個(gè)半月。而我們面臨的是100多個(gè)組件、turboModule的重實(shí)現(xiàn)。

2)HarmonyOS Next逐步完善,與Android、iOS在某些特性上有差異

開發(fā)過(guò)程中發(fā)現(xiàn)了很多HarmonyOS Next功能不完善、存在若干Bug的地方,畢竟是一個(gè)新系統(tǒng),我們與華為同學(xué)緊密合作,一一解決了問(wèn)題,這個(gè)過(guò)程見證了鴻蒙系統(tǒng)的愈發(fā)成熟。

出于安全考慮,鴻蒙系統(tǒng)有一些新特性,比如選取圖片視頻進(jìn)行編輯的場(chǎng)景,在Android、iOS中,申請(qǐng)用戶權(quán)限之后便可以拿到整個(gè)系統(tǒng)相冊(cè)的圖片視頻,這確實(shí)可能存在一些安全隱患。鴻蒙在最開始就切割了這一操作,即使App經(jīng)用戶同意申請(qǐng)了讀相冊(cè)權(quán)限,也無(wú)法拿到系統(tǒng)主相冊(cè)的圖片視頻,本意是讓App直接跳到系統(tǒng)相冊(cè)選取圖片之后返回,只提供當(dāng)次選中的圖片信息給App,從而徹底斷絕了App侵犯用戶隱私的可能。

但我們的多媒體場(chǎng)景比較復(fù)雜,用戶選取圖片、視頻后會(huì)跳入編輯頁(yè),且可以重回相冊(cè)頁(yè)選擇其他圖片,也就是說(shuō)我們的圖片視頻選擇頁(yè)與編輯頁(yè)存在聯(lián)動(dòng),鴻蒙提供的這種跳入系統(tǒng)相冊(cè)的方式顯示無(wú)法滿足我們的需求。

后續(xù)經(jīng)過(guò)討論,鴻蒙提供了相冊(cè)Picker的方案,將系統(tǒng)相冊(cè)頁(yè)封裝為組件提供給開發(fā)者,我們的圖片視頻選擇頁(yè)可以內(nèi)嵌相冊(cè)Picker,從而解決了聯(lián)動(dòng)的問(wèn)題。但這個(gè)需求從開始評(píng)審、開發(fā)、測(cè)試到最終實(shí)現(xiàn),花費(fèi)了幾個(gè)月的時(shí)間。

3)RN組件C化

在接入RN的過(guò)程中,發(fā)現(xiàn)鴻蒙中RN關(guān)鍵性能指標(biāo)與Android、iOS有差距,華為鴻蒙RN團(tuán)隊(duì)為了解決性能問(wèn)題,提出了組件C化的方案。

簡(jiǎn)單來(lái)講,就是將ArkTS實(shí)現(xiàn)的組件用C-Api重新實(shí)現(xiàn)一遍,華為方面給出的要求是容器結(jié)點(diǎn)(RN代碼中存在標(biāo)簽<></>嵌套的組件)需強(qiáng)制C化。雖然攜程中這種必須C化的組件并不多,但也帶來(lái)了非常多的適配工作。具體可參考下篇-組件C化。

部分組件圖如下:

圖片


Fabric、TurboModule

最開始,我們?cè)趯?shí)現(xiàn)相關(guān)Fabric、TurboModule的時(shí)候,鴻蒙RN框架還沒(méi)有提供Spec文件CodeGen工具,全靠手寫。不過(guò)現(xiàn)在已經(jīng)提供了相關(guān)工具,具體操作步驟可以參考相關(guān)文檔。

Spec文件生成之后,剩下的工作就是相關(guān)組件、TurboModule的功能橋接實(shí)現(xiàn),邏輯較為簡(jiǎn)單,實(shí)現(xiàn)相關(guān)功能就好。

需要注意的是:

  • RN代碼中存在標(biāo)簽<></>嵌套的組件被視為容器結(jié)點(diǎn),此類型組件需使用C-API實(shí)現(xiàn)。
  • 可以通過(guò)this.ctx獲取RNOHContext,進(jìn)而獲取RNInstance,從而獲取一系列RN端JS傳入的信息,如View寬高、style等,也可執(zhí)行發(fā)送事件、接收事件、獲取TurboModule進(jìn)行其他操作等等。
  • 在RNInstanceImpl構(gòu)造函數(shù)中有一個(gè)arkTsComponentNames字段,可以傳入所有我們自定義葉子結(jié)點(diǎn)Fabric組件的名稱,用于在RNOH SDK內(nèi)部進(jìn)行指令分發(fā)優(yōu)化。實(shí)現(xiàn)ArkTS端Fabric組件后,需要將Fabric組件的名稱加入此列表中。
  • 假設(shè)存在實(shí)現(xiàn)過(guò)于復(fù)雜或者其他原因無(wú)法C化的容器組件,RNOH SDK內(nèi)部指令優(yōu)化代碼需修改(這也意味著RNOH SDK需重新打包編譯),關(guān)鍵代碼見下文‘性能優(yōu)化-5.3 RN 指令精簡(jiǎn)章節(jié)。

3.4 組件C化

經(jīng)過(guò)與華為的詳細(xì)溝通,RN代碼中存在標(biāo)簽<></>嵌套的組件被視為容器結(jié)點(diǎn),此類型組件需強(qiáng)制C化。

也就是此類型的組件:

<RNComponent>

    <Text/>

    <Image/>

</RNComponent>

RNComponent算為容器結(jié)點(diǎn)

經(jīng)確認(rèn),攜程端存在四個(gè)需強(qiáng)制C化的容器結(jié)點(diǎn)組件,分別為:

名稱

描述

SwipeoutView

可滑動(dòng)組件

ScrollView

滾動(dòng)組件

CustomScrollView

自定義列表組件

CRNModal

modal容器

簡(jiǎn)而言之,需要把ArkTS端實(shí)現(xiàn)的組件用C-Api再次實(shí)現(xiàn)。

3.4.1 CRNModal C化

CRNModal 在開發(fā)測(cè)試過(guò)程中一步步探索了實(shí)現(xiàn)方案,經(jīng)過(guò)多輪測(cè)試、方案討論調(diào)整,最終確定了C化方案。

方案一:嘗試使用系統(tǒng)Modal實(shí)現(xiàn)這個(gè)組件

后續(xù)測(cè)試過(guò)程中發(fā)現(xiàn)系統(tǒng)Modal的實(shí)現(xiàn)方案為系統(tǒng)Dialog,層級(jí)很高,攜程業(yè)務(wù)線會(huì)出現(xiàn)這樣一種場(chǎng)景,RN頁(yè)面打開Modal后點(diǎn)擊跳轉(zhuǎn)一個(gè)其他頁(yè)面,新打開的頁(yè)面會(huì)出現(xiàn)在Modal的下方,不符合需求,方案淘汰。

方案二:嘗試通過(guò)新跳轉(zhuǎn)一個(gè)透明頁(yè)面的方式實(shí)現(xiàn)modal

測(cè)試發(fā)現(xiàn)新跳轉(zhuǎn)一個(gè)頁(yè)面后,RN的點(diǎn)擊事件分發(fā)出現(xiàn)問(wèn)題,無(wú)法響應(yīng)任何事件。且我們App的路由方案為Navigation,經(jīng)與華為方面溝通,Navigation C化難度非常巨大,短時(shí)間不可行。此方案淘汰。

方案三:嘗試在RN JS端創(chuàng)建modal

JS端創(chuàng)建一個(gè)style為position: 'absolute', zIndex: 999的容器,層級(jí)提高,顯示在其他組件上方來(lái)實(shí)現(xiàn)modal。測(cè)試發(fā)現(xiàn)調(diào)用顯示Modal的地方很多,可能會(huì)在一個(gè)嵌套很深的層級(jí)中,如果在這里嘗試展示這個(gè)zIndex: 999的容器,還是會(huì)有被遮擋的情況,最終此方案也被淘汰。

方案四:C++層進(jìn)行插入

經(jīng)過(guò)內(nèi)部討論,這個(gè)modal應(yīng)該展示在整個(gè)RN頁(yè)面層級(jí)的最上方,這在Android、iOS中都很好實(shí)現(xiàn),但鴻蒙是一個(gè)聲明式的語(yǔ)言,無(wú)法拿到頁(yè)面實(shí)例,無(wú)法拿到父組件,也就無(wú)法進(jìn)行插入。

但研究RNOH SDK之后發(fā)現(xiàn),C化后的RNInstance實(shí)例在C++端持有一個(gè)XComponentSurface,所有RN頁(yè)面對(duì)應(yīng)的Native組件都被添加顯示在這里,而XComponentSurface可以獲取rootView實(shí)例ComponentInstance,這是一個(gè)根控件,將這個(gè)根控件強(qiáng)轉(zhuǎn)為ViewComponentInstance之后,在ViewComponentInstance.cpp代碼中可以類似Android,獲取childCount,通過(guò)index添加child等等,進(jìn)而可以實(shí)現(xiàn)在RN頁(yè)面層級(jí)最上方添加modal,最終也是依據(jù)此方案,實(shí)現(xiàn)了CRNModal組件。

流程如下:

圖片


3.4.2 開發(fā)注意事項(xiàng)

1)在CAPI instance中聲明的Node節(jié)點(diǎn),必須在全局聲明,否則會(huì)導(dǎo)致node節(jié)點(diǎn)不能收到node_event等消息;

2)設(shè)置node屬性構(gòu)建ArkUI_AttributeItem的時(shí)候,如果設(shè)置的值是一個(gè)ArkUI_NumberValue類型,需要指定size,這個(gè)size的計(jì)算必須除去類型的長(zhǎng)度,如下:

ArkUI\_NumberValue value\[] = {{.i32 = alignItem}};
ArkUI\_AttributeItem item = {value, sizeof(value) / sizeof(ArkUI\_NumberValue)};

3)animateTo執(zhí)行動(dòng)畫,在組件析構(gòu)之后還是會(huì)回調(diào),需要控制好生命周期避免crash;

4)設(shè)置Stack背景,導(dǎo)致子組件布局錯(cuò)誤,是因?yàn)镾tack被作為同級(jí)組件從而導(dǎo)致子組件的postion參數(shù)異常,需要手動(dòng)處理好position問(wèn)題;

5)可以通過(guò)以下方式在C++層調(diào)用arkTS方法,獲取相關(guān)數(shù)據(jù):

方案1:在ArkTS里實(shí)現(xiàn)一個(gè)TurboModule方法,然后通過(guò)rnInstance->getTurboModule<XXTurboModule>獲取對(duì)應(yīng)的TurboModule,調(diào)用方法,獲取返回值。但此方案涉及C++與ArkTS的跨端調(diào)用,性能會(huì)差一些,優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單。

方案2:通過(guò)ArkTSBridge,添加一個(gè)ArkTS方法的橋,然后就可以在C++里直接調(diào)用這個(gè)ArkTS方法。具體實(shí)現(xiàn)可以參考NapiBridget.ArkTSBridgeHandler里任意方法。此方案性能好,但實(shí)現(xiàn)起來(lái)稍微麻煩一點(diǎn)。

6)可以通過(guò)以下Api獲取設(shè)備的高寬

auto displayMetrics = ArkTSBridge::getInstance()->getDisplayMetrics();
 displayMetrics.screenPhysicalPixels.width / displayMetrics.screenPhysicalPixels.scale //直接獲取到是px單位,需要進(jìn)行轉(zhuǎn)換,也可以自行修改TurboModle的初始化值:

四、遇到的問(wèn)題和解決辦法

在升級(jí)適配過(guò)程中,我們遇到了一些RN新架構(gòu)問(wèn)題,還有一些鴻蒙RN特有的問(wèn)題。

RN 新架構(gòu)問(wèn)題:

  • IOS Animated.timing 設(shè)置 useNativeDriver:true 后,內(nèi)嵌按鈕無(wú)法點(diǎn)擊
  • IOS TouchableOpacity 內(nèi)嵌 Aminated.View ,Aminated.View 開啟動(dòng)畫變更位置后,無(wú)法點(diǎn)擊
  • IOS Image樣式設(shè)置 borderRadius 顯示不全
  • IOS minimumFontScale maxFontSizeMultiplier 不生效
  • Aminated.View 內(nèi)嵌Modal組件,內(nèi)部TouchableOpacity點(diǎn)擊不響應(yīng)
  • FlatList、ScrollView stickyHeaderIndices 吸頂功能多次滑動(dòng)后失效
  • Aminated.View 、Animated.ScrollView、layoutAnimation 動(dòng)畫卡頓
  • 樣式中使用了zIndex屬性層級(jí)可能不生效,嘗試添加 position:relative屬性后生效
  • 組件需要設(shè)置默認(rèn)高寬,不然布局展示可能發(fā)生截?cái)?/span>

由于動(dòng)畫、樣式、性能影響較大,最終決定在RN 0.72.5版本(iOS/Andriod)中只使用Turbo Modules,不開啟Fabric模式,來(lái)規(guī)避掉這些問(wèn)題。

但鴻蒙RN只支持新架構(gòu),新架構(gòu)存在問(wèn)題有些在鴻蒙端同樣存在。我們和華為伙伴緊密溝通來(lái)處理這些問(wèn)題。對(duì)于無(wú)法規(guī)避問(wèn)題只能業(yè)務(wù)側(cè)做兼容處理。

鴻蒙RN特有的問(wèn)題:

問(wèn)題:RN Modal彈窗顯示時(shí),再打開一個(gè)H5頁(yè)面會(huì)顯示在Modal下面 

解決辦法:實(shí)現(xiàn)一個(gè)View層級(jí)的CRNModal替代RN Modal

問(wèn)題:絕對(duì)定位中添加top:“auto”導(dǎo)致元素不顯示 

解決辦法:去除top:“auto”設(shè)置

問(wèn)題:zIndex:-1元素不顯示 

解決辦法:在最外層的View添加collapsable={false}屬性

問(wèn)題:position:absolute樣式漂移 

解決辦法:在外層的View添加collapsable={true}屬性

問(wèn)題:react-native-harmony/metro.config 和現(xiàn)有的自定義metro配置沖突

解決辦法:提取react-native-harmony/metro.config中harmony平臺(tái)相關(guān)處理,合并到自定義metro插件中

五、性能優(yōu)化

華為內(nèi)部對(duì)鴻蒙系統(tǒng)寄予厚望,為了追求更好的用戶體驗(yàn),希望鴻蒙APP核心業(yè)務(wù)場(chǎng)景性能指標(biāo)達(dá)成業(yè)內(nèi)最佳水平。對(duì)攜程來(lái)說(shuō),大多數(shù)業(yè)務(wù)頁(yè)面都是RN,RN技術(shù)棧對(duì)性能指標(biāo)非常敏感,很小的性能優(yōu)化或劣化,都會(huì)大幅影響用戶體驗(yàn)。

5.1 CRN預(yù)加載

默認(rèn)情況下,我們會(huì)在頁(yè)面的生命周期中去加載rn_bundle,因?yàn)轫?yè)面已經(jīng)進(jìn)入生命周期開始展示了,加載bundle又會(huì)有一定的耗時(shí),這種情況下,就會(huì)產(chǎn)生白屏現(xiàn)象。

攜程也存在某些頁(yè)面依賴接口數(shù)據(jù)且接口返回比較慢的情況,比如機(jī)票列表頁(yè),在進(jìn)入頁(yè)面白屏之后又會(huì)有長(zhǎng)時(shí)間的骨架屏,用戶體驗(yàn)差。

圖片

經(jīng)過(guò)調(diào)研,攜程端基于系統(tǒng)的FrameNode能力,實(shí)現(xiàn)了CRN預(yù)加載方案,解決了上述問(wèn)題。

5.1.1 FrameNode

圖片


鴻蒙中FrameNode是一個(gè)非常強(qiáng)大的能力,不光是各大廠商在用,官方ArkUI中也大量使用了FrameNode進(jìn)行性能優(yōu)化。

它的特點(diǎn)用一句話可以描述:后臺(tái)離屏渲染,前臺(tái)上樹展示。

利用這個(gè)特點(diǎn),可以實(shí)現(xiàn)組件渲染與頁(yè)面展示的完全分離。也就是說(shuō)組件的創(chuàng)建渲染不再依賴頁(yè)面的生命周期,這樣我們就可以做很多事情了。

但正因?yàn)镕rameNode組件會(huì)在后臺(tái)真實(shí)渲染,它使用起來(lái)會(huì)有一定的風(fēng)險(xiǎn),后臺(tái)渲染的組件可能會(huì)影響前臺(tái)行為,比如改變狀態(tài)欄顏色、彈Toast、彈Dialog等,這些都需要人為進(jìn)行規(guī)避。

在RNSDK中我們對(duì)Toast、Dialog、狀態(tài)欄等行為的TurboModule調(diào)用,根據(jù)頁(yè)面狀態(tài)進(jìn)行了攔截,頁(yè)面不可見時(shí),上述這些TurboModule的調(diào)用都不會(huì)生效,而且會(huì)記錄最后一次攔截的行為及參數(shù),在頁(yè)面變?yōu)榭梢姇r(shí),恢復(fù)最后一次被攔截的行為。

基于FrameNode能力,實(shí)現(xiàn)了鴻蒙中CRN預(yù)加載的1.0和2.0方案,下文會(huì)詳細(xì)介紹這兩個(gè)方案。

另外需要注意的一點(diǎn)是,攜程在RN中使用FrameNode過(guò)程中,遇過(guò)一個(gè)困擾許久的問(wèn)題:使用FrameNode加載RN頁(yè)面時(shí),在某些比較復(fù)雜的頁(yè)面,會(huì)發(fā)生非常嚴(yán)重的JS阻塞現(xiàn)象,用戶的點(diǎn)擊、返回等操作行為被頁(yè)面渲染指令阻塞,遲遲得不到響應(yīng),極度影響用戶體驗(yàn)。

經(jīng)過(guò)與華為方面的聯(lián)合排查,發(fā)現(xiàn)是因?yàn)镽NInstance初始化時(shí)傳入的參數(shù):disableCnotallow=true導(dǎo)致。此參數(shù)會(huì)關(guān)閉React18一個(gè)性能優(yōu)化的功能:微任務(wù)指令批量提交,從而導(dǎo)致在JS代碼setTimeout中進(jìn)行setState時(shí),指令立即提交,總指令數(shù)大幅增加,進(jìn)而大幅影響RN指令處理效率。

大家如果也會(huì)在項(xiàng)目中用到FrameNode進(jìn)行RN頁(yè)面的性能優(yōu)化,在初始化RNInstance時(shí),disableConcurrentRoot參數(shù)一定要傳false。

5.1.2 CRN分包

要理解我們CRN的預(yù)加載方案,首先要了解我們的分包邏輯,具體可參考文章:《近萬(wàn)字長(zhǎng)文詳述攜程大規(guī)模應(yīng)用RN的工程化實(shí)踐》。

總體而言,將業(yè)務(wù)bundle的加載分為兩部分: rn_common & rn_business。其中rn_common包含完整的基礎(chǔ)框架能力,rn_business則是具體的業(yè)務(wù)邏輯代碼。通過(guò)nativeRequire的方式,分行加載rn_business中的業(yè)務(wù)代碼,然后在加載了rn_common的空白頁(yè)面上進(jìn)行渲染。

可以發(fā)現(xiàn),CRN這種分包模式完美契合FrameNode,我們使用FrameNode預(yù)渲染一個(gè)加載了rn_common的空白頁(yè)面,這個(gè)空白頁(yè)面不會(huì)渲染UI元素且具備完整的框架能力,不會(huì)有任何影響前臺(tái)頁(yè)面的行為,等到頁(yè)面真正展示時(shí),才去加載業(yè)務(wù)代碼rn_business,進(jìn)行UI渲染,從而完美規(guī)避FrameNode的使用風(fēng)險(xiǎn)。

也正是基于此,我們實(shí)現(xiàn)了CRN預(yù)加載1.0的方案。

5.1.3 CRN預(yù)加載1.0

圖片


預(yù)加載1.0方案:

  • 在前置頁(yè)面通過(guò)FrameNode預(yù)加載一個(gè)RNSurface,利用這個(gè)RNSurface去加載rn_common,完成后可以理解為后臺(tái)存在了一個(gè)具備所有框架能力的空白頁(yè)面。
  • 用戶點(diǎn)擊跳轉(zhuǎn)RN頁(yè)面時(shí),添加一個(gè)用戶幾乎不可感知的延時(shí)去加載rn_business。
  • 充分利用這個(gè)跳轉(zhuǎn)延時(shí) + 頁(yè)面創(chuàng)建 + 頁(yè)面切換的動(dòng)畫時(shí)間去加載業(yè)務(wù)bundle、渲染等。
  • 業(yè)務(wù)Bundle加載完成后,動(dòng)態(tài)替換業(yè)務(wù)自定義的intialProps
  • 做到了rn bundle加載、 渲染與頁(yè)面生命周期的完全隔離。
  • 目前預(yù)加載1.0方案在全業(yè)務(wù)默認(rèn)使用,基本解決了RN頁(yè)面首幀白屏問(wèn)題。

在攜程的某些業(yè)務(wù)線中,頁(yè)面UI依賴網(wǎng)絡(luò)接口數(shù)據(jù),且受外部接口影響,響應(yīng)較慢。這時(shí)候,打開頁(yè)面會(huì)有較長(zhǎng)時(shí)間的骨架屏loading,也非常影響用戶體驗(yàn)。

如果在前置頁(yè)面中,我們可以大概率猜到用戶下一步跳入的目標(biāo)頁(yè)面,那是不是可以利用FrameNode將目標(biāo)頁(yè)面提前加載,且根據(jù)前置頁(yè)面的參數(shù)進(jìn)行動(dòng)態(tài)刷新,這樣用戶真正跳入目標(biāo)頁(yè)面的時(shí)候,就可以直接上屏,達(dá)到秒開的效果。

基于此,我們實(shí)現(xiàn)了CRN預(yù)加載2.0。

5.1.4 CRN預(yù)加載2.0

圖片

預(yù)加載2.0方案:

  • 在前置頁(yè)面通過(guò)FrameNode預(yù)加載了一個(gè)真實(shí)的RN頁(yè)面,完成了加載rn_commom、rn_business、接口請(qǐng)求、渲染等一系列流程。
  • 前置頁(yè)面中影響下一個(gè)頁(yè)面關(guān)鍵參數(shù)發(fā)生改變時(shí),發(fā)消息給后臺(tái)預(yù)加載的的RN頁(yè)面,RN頁(yè)面接收到事件,拿到關(guān)鍵參數(shù)后進(jìn)行網(wǎng)絡(luò)請(qǐng)求,得到數(shù)據(jù)后對(duì)頁(yè)面進(jìn)行刷新。
  • 用戶點(diǎn)擊跳轉(zhuǎn)到目標(biāo)頁(yè)面時(shí),直接將后臺(tái)已經(jīng)預(yù)渲染好的頁(yè)面上屏展示。
  • 因?yàn)轫?yè)面已經(jīng)在后臺(tái)被真實(shí)渲染,有影響前置頁(yè)面的風(fēng)險(xiǎn),雖然我們?cè)赗N SDK層面已經(jīng)做了一層攔截,但這種攔截不可能cover所有場(chǎng)景,所有接入了預(yù)加載2.0方案的業(yè)務(wù)都必須在上線前經(jīng)過(guò)完整回歸測(cè)試。
  • 目前,我們?cè)跈C(jī)票列表頁(yè)及火車票詳情頁(yè)使用了預(yù)加載2.0方案。

對(duì)比視頻:

性能優(yōu)化關(guān)閉:


圖片

性能優(yōu)化開啟:

圖片

5.2 RN TurboModule運(yùn)行在Worker線程

前段時(shí)間我們?cè)赗N JS端對(duì)TurboModule調(diào)用加了一個(gè)埋點(diǎn),統(tǒng)計(jì)TurboModule方法調(diào)用的耗時(shí),后續(xù)也是根據(jù)這個(gè)埋點(diǎn)生成了一個(gè)報(bào)表,發(fā)現(xiàn)在鴻蒙中,TurboModule同步方法調(diào)用耗時(shí)比Android、iOS耗時(shí)長(zhǎng)10倍,某些方法甚至慢100倍。

圖片


經(jīng)過(guò)分析,在Android、iOS中TurboModule都是運(yùn)行在單獨(dú)的子線程中,而在鴻蒙中,TurboModule都運(yùn)行在主線程,主線程要承載一些別的任務(wù)比如頁(yè)面渲染、用戶操作行為響應(yīng)等,這些行為會(huì)導(dǎo)致鴻蒙中TurboModule的調(diào)用被阻塞,耗時(shí)就長(zhǎng)。比如下圖的Trace,如果TurboModule在UI線程運(yùn)行,那就可能會(huì)被阻塞,阻塞的這段時(shí)間,js線程只能等待,而這段等待是毫無(wú)意義的。

圖片


前段時(shí)間,鴻蒙RN SDK也是加入了TurboModule運(yùn)行在Worker線程這個(gè)能力。RNInstance在創(chuàng)建時(shí)會(huì)同步創(chuàng)建一個(gè)worker線程,專門用于TurboModule運(yùn)行。我們要做的是對(duì)工程中TurboModule代碼進(jìn)行適配改造,使之可以運(yùn)行到worker線程中。

整個(gè)適配過(guò)程也存在一系列的問(wèn)題。

首先,鴻蒙的ArkTS衍生自TS語(yǔ)言,基于Actor線程模型,內(nèi)存不共享,線程間數(shù)據(jù)通信非常麻煩。

為了解決線程間通信流程繁瑣的問(wèn)題,鴻蒙提供了Sendable注解,可以理解為被這個(gè)注解修飾的對(duì)象會(huì)在共享內(nèi)存創(chuàng)建。但Sendable存在一個(gè)問(wèn)題,Sendable對(duì)象的成員變量只能是Sendable對(duì)象或其他特定的數(shù)據(jù)類型,也就是說(shuō)我們?nèi)绻麑?duì)一個(gè)對(duì)象進(jìn)行Sendable改造,就必須對(duì)他的所有成員變量進(jìn)行Sendable改造,也需要對(duì)成員變量的成員變量進(jìn)行Sendable改造,那這個(gè)改造過(guò)程就存在指數(shù)級(jí)擴(kuò)散的問(wèn)題。

另外,Sendable注解提供的時(shí)候,我們大部分代碼都已經(jīng)完成了,在這種成熟的大型項(xiàng)目中再重新進(jìn)行Sendable改造的成本非常高,大家各自App如果還沒(méi)開始或者剛開始開發(fā),一定要考慮Sendable適配的問(wèn)題,比如數(shù)據(jù)類型默認(rèn)使用collection下屬map、array,class默認(rèn)添加Sendable注解等。

目前,我們適配完成了7個(gè)TurboModule,其他TurboModule做Sendable適配的成本非常高,正在逐步進(jìn)行中。

5.3 RN 指令精簡(jiǎn)

在鴻蒙中,RN支持兩套組件:C-API實(shí)現(xiàn)的組件以及原生ArkTS實(shí)現(xiàn)的組件。

C-API實(shí)現(xiàn)的組件性能更好,華為的支持力度更大,出于性能考慮,大多數(shù)廠商使用RN時(shí),都會(huì)選擇C-API實(shí)現(xiàn)的組件。

C-API組件的Create、Insert、Update、Remove等指令不再需要傳遞給ArkTS側(cè)。僅僅幾個(gè)自定義的ArkTS組件需要將指令傳遞給ArkTS側(cè)。如下圖中綠色節(jié)點(diǎn)的指令。

圖片

鴻蒙RNOH SDK中有默認(rèn)算法,可以保留葉子節(jié)點(diǎn)ArkTS組件的指令,如果項(xiàng)目?jī)?nèi)沒(méi)有ArkTS容器組件,RNInstance初始化時(shí)添加配置arkTsComponentNames就好。

但在攜程的業(yè)務(wù)中存在AdatpterMap這個(gè)容器組件,依賴系統(tǒng)花瓣地圖,這個(gè)地圖C化難度巨大,所以我們的AdatpterMap暫時(shí)也只能由ArkTS實(shí)現(xiàn)。

這就需要我們自己設(shè)計(jì)算法。在保證性能的情況下,完整保留AdatpterMap及其子組件的相關(guān)指令。

以下是關(guān)鍵代碼:

RNOHSDK/src/main/cpp/RNOH/MountingManagerCAPI.cpp::getValidMutations:

facebook::react::ShadowViewMutationList MountingManagerCAPI::getValidMutations(
  facebook::react::ShadowViewMutationList const& mutations) {


...
//需要特殊處理,保留容器組件及其子組件所有指令的容器組件名稱
std::unordered_set<std::string> whiteListArkTsComponentNames = {
    "AdapterMap", "AdapterMapMarkersContainer", "AdapterMapMarker"};
//第一次遍歷:只遍歷create,從前到后找到混合組件名稱,只保存tag
for (auto mutation : mutations) {
  if (mutation.type == facebook::react::ShadowViewMutation::Create) {
    ...
    //特殊保留地圖容器組件tag
    if (whiteListArkTsComponentNames
            .count(newChild.componentName)) {
      arkTsComponentTags.push(newChild.tag);
    }
  }
}


if (!arkTsComponentTags.empty()) {
  // 第二次遍歷:只遍歷insert,找到混合組件tag和它的子組件的tag。采用廣度遍歷方式,這里也只保存tag
  for (auto mutation : mutations) {
    if (mutation.type == facebook::react::ShadowViewMutation::Insert) {
       ...
       //保存地圖容器組件tag和它的子組件的tag
    }
  }
}


//第三次遍歷:根據(jù)2中齊全的tag,重新過(guò)濾所有指令,保留這些tag的create、insert、update、remove指令。
for (auto mutation : mutations) {
  ...
  //根據(jù)組件Tag,保留需要傳遞給ArkTS的所有指令
}
 return validMutations;
}

再來(lái)看下成果,測(cè)試RN頁(yè)面中,算法過(guò)濾的不需要傳遞到ArkTS側(cè)的指令數(shù)超過(guò)99.9%。

頁(yè)面

優(yōu)化前指令數(shù)

優(yōu)化后指令數(shù)

酒店首頁(yè)

3092

11

酒店套餐

2181

0

機(jī)票+酒店

496

2

酒店

3838

3

美食/購(gòu)物

1542

3

5.4 分幀渲染

分幀渲染主要用在App的啟動(dòng)優(yōu)化中。首頁(yè)宮格存在兩屏,二屏在剛開始是不可見的,可以在首頁(yè)加載完畢之后再去加載宮格二屏。

圖片

分幀渲染可以監(jiān)聽到幀渲染的回調(diào),這樣就可以對(duì)頁(yè)面元素的加載優(yōu)先級(jí)進(jìn)行定制,將重要的元素優(yōu)先加載,不重要或者不可見的元素后續(xù)加載。進(jìn)而提升頁(yè)面的性能。

關(guān)鍵代碼:

private myDisplaySync?: displaySync.DisplaySync


updateStage() {
    if (this.stages == 0) {
      this.myDisplaySync = displaySync.create();
      this.myDisplaySync.start();
      this.myDisplaySync.on('frame', (frameInfo: displaySync.IntervalInfo) => {
        this.updateStage();
      });
    }


    this.stages++;
    if (this.stages == 3) {
      this.myDisplaySync?.stop();
    }
}
...


 build() {
    Column() {
      Scroll(this.scroller) {
        Row() {
          //默認(rèn)加載宮格首屏
          if (this.stages > 0){
            this.genFirstCell(0)
          }
          //三個(gè)渲染幀之后,加載宮格二屏
          if (this.stages > 2){
            this.genFirstCell(1)
          }
        }
        ...
        }

接入分幀渲染,控制宮格二屏的渲染時(shí)機(jī)后,首頁(yè)的啟動(dòng)耗時(shí)減少了20ms。

5.5 后續(xù)性能優(yōu)化

華為鴻蒙RN團(tuán)隊(duì)規(guī)劃有一個(gè)性能優(yōu)化的feature,在這里簡(jiǎn)單介紹下。

5.5.1 更換RN JS執(zhí)行引擎:JSVM(基于V8)

JSVM相較hermers,預(yù)計(jì)可以提升20%的JS解析性能。前段時(shí)間華為提供了一個(gè)rn sdk,我們新建了一個(gè)分支驗(yàn)證了一下這個(gè)JSVM,js的加載速度確實(shí)比hermes要快一些。但RN產(chǎn)物jsbundle的加載比hermes要慢,這意味著頁(yè)面的首屏性能會(huì)受到一定影響。這是我們非常關(guān)注的一個(gè)性能指標(biāo),問(wèn)題得到解決之后,我們也會(huì)進(jìn)行切換。

六、成果和未來(lái)規(guī)劃

經(jīng)過(guò)4個(gè)月鴻蒙版本的開發(fā)和適配,2024年6月18日攜程在鴻蒙應(yīng)用商店上架了首個(gè)全業(yè)務(wù)全場(chǎng)景的攜程旅行鴻蒙版應(yīng)用。業(yè)務(wù)方在Android/iOS上的一套CRN代碼,只需經(jīng)過(guò)簡(jiǎn)單的適配,就能正常在鴻蒙系統(tǒng)上運(yùn)行,甚至有些業(yè)務(wù)不需要修改,現(xiàn)有的代碼直接在鴻蒙系統(tǒng)上能完整的跑完業(yè)務(wù)流程。

未來(lái),我們還會(huì)在以下兩個(gè)方面持續(xù)對(duì)鴻蒙CRN框架進(jìn)行優(yōu)化:

用戶體驗(yàn)

用戶體驗(yàn)和性能一直是我們關(guān)注的重點(diǎn),CRN在鴻蒙系統(tǒng)上還有很大的優(yōu)化空間。我們會(huì)持續(xù)在性能上繼續(xù)打磨和提升。

技術(shù)布局

為了追求高效率、低成本的研發(fā)模式,未來(lái)攜程業(yè)務(wù)開發(fā)會(huì)大量使用一碼多端的框架xTaro。后續(xù)xTaro會(huì)支持鴻蒙系統(tǒng),真正實(shí)現(xiàn)讓業(yè)務(wù)的一套代碼能在多端多平臺(tái)多應(yīng)用場(chǎng)景上全矩陣運(yùn)行。

鴻蒙生態(tài)的發(fā)展是一個(gè)持續(xù)且快速的過(guò)程。隨著鴻蒙系統(tǒng)的不斷迭代升級(jí)和生態(tài)的逐步完善,我們會(huì)持續(xù)為用戶提供更加智能、安全、便捷的一站式的旅行應(yīng)用。

責(zé)任編輯:張燕妮 來(lái)源: 攜程技術(shù)
相關(guān)推薦

2022-07-15 12:58:02

鴻蒙攜程華為

2023-06-28 14:01:13

攜程實(shí)踐

2022-09-13 13:14:53

存儲(chǔ)服務(wù)

2020-11-11 13:44:00

攜程旅行點(diǎn)擊量

2024-07-25 11:58:35

2022-05-13 09:27:55

Widget機(jī)票業(yè)務(wù)App

2022-08-20 07:46:03

Dynamo攜程數(shù)據(jù)庫(kù)

2023-07-07 12:26:39

攜程開發(fā)

2009-06-19 09:52:46

Acegi安全框架Spring框架

2022-07-15 09:20:17

性能優(yōu)化方案

2022-07-08 09:38:27

攜程酒店Flutter技術(shù)跨平臺(tái)整合

2022-08-12 08:34:32

攜程數(shù)據(jù)庫(kù)上云

2023-02-08 16:34:05

數(shù)據(jù)庫(kù)工具

2024-07-05 15:05:00

2022-06-17 10:44:49

實(shí)體鏈接系統(tǒng)旅游AI知識(shí)圖譜攜程

2022-12-15 21:02:54

圖調(diào)度框架

2022-06-03 09:21:47

Svelte前端攜程

2023-04-14 10:29:24

小程序實(shí)踐

2023-12-15 10:05:58

攜程網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)