作者简介
携程鸿蒙框架技术团队,负责携程旅行鸿蒙体系原生应用开辟,为鸿蒙生态用户提供一站式旅行服务。
团队热招岗位:资深移动端工程师、高级iOS开辟工程师
携程作为鸿蒙生态在旅游行业的告急合作搭档,早在鸿蒙服务卡片时期就和华为开始合作。2023年9月,华为公布鸿蒙原生应用启动开辟,同年12月,我们完成携程旅行鸿蒙Beta版本的开辟,技术上基于Web+部分原生的方案实现。24年6月HarmonyOS Next体系正式内测后,为了让鸿蒙生态的用户使用到携程一站式的旅行服务,我们开始在鸿蒙体系上对全业务进行适配。
- 一、RN在携程业务使用现状
- 二、技术选型(为什么选择CRN)
- 三、CRN适配实践
- 3.1 版本升级
- 3.2 差异化工作
- 3.3 原生组件开辟
- 3.4 组件C化
- 四、遇到的问题息争决办法
- 五、性能优化
- 5.1 CRN预加载
- 5.2 RN TurboModule运行在Worker线程
- 5.3 RN 指令精简
- 5.4 分帧渲染
- 5.5 后续性能优化
- 六、成果和未来规划
一、RN在携程业务使用现状
2019年,携程开始在线上使用RN框架,并结合自身的业场景,对RN框架进行了开辟和改造,研发了CRN框架(以下简称CRN)。2021年,CRN成为携程主流的开辟框架。集团内有20+个App接入CRN框架,其中核心的App都已接入。携程旅行App中,200+个业务Bundle在线上运行,业务页面数量凌驾2000个,凌驾80%的业务使用CRN。
二、技术选型(为什么选择CRN)
重新技术的选择到落地的实践上看,业务对技术的要求往往是以下几个方面:
1)功能全,全量业务都能快速的适配上线
2)性能好,用户体验多端同等
3)成本低,复用现有在其他平台的运行的代码
为了满意业务需求,鸿蒙的实现技术上我们选择了CRN,主要思量:
1)基建成熟度高:有配套研发/测试/发布/运营监控体系,内部交流活跃,知识沉淀深
2)业务适配成本小:业务不必要重新再开辟一遍,可以使用现有的业务代码
3)开辟能快速上手:业务开辟还是使用原有的技术进行开辟,在鸿蒙上运行
4)产品迭代效率:支持每个周期的产品迭代,快速在鸿蒙体系的手机上线
三、CRN适配实践
3.1 版本升级
线上携程旅行App使用的React Native(RN)版本是0.70.1,而鸿蒙RN版本是0.72.5。因此,适配鸿蒙的第一步是将RN版本从0.70.1升级到0.72.5。
版本升级包含了如下几个方面:
3.1.1 RN版本差异分析
我们对比RN 0.70.1 和 0.72.5 框架库的差异,整体改动点不多。为了降低业务方升级成本,我们在框架底层对废弃的组件和API变动做了兼容,尽可能减少业务使用方的改动。
3.1.2 CRN框架改造
CRN框架覆盖了文档、工具、开辟框架、发布、监控、排障全链路。对应框架的改造也从这几个方面进行。
1)在文档方面,我们编写了详细的业务升级文档,列出业务方必要关注的点和常见问题。
2)在工具方面,提供了一键式CLI升级工具,只需在业务工程实行一行升级命令,即可完成工程升级改造。
3)在开辟框架方面,改造涉及点比力多,包括:
- 对Native运行时升级,升级RN 0.72.5 核心库,归并对官方RN库的自定义改动点。
- 对JS打包工具升级,支持现有的拆包逻辑,归并对官方RN库的自定义改动点。
- 梳理使用到的社区三方库,同一三方库版本升级至鸿蒙RN三方库要求版本。
- 对Hermes引擎进行升级,归并自定义改动点。
- 对RN自定义组件和API进行新架构改造。
4)在发布方面,对现有的CRN发布体系进行改造,支持选择鸿蒙平台进行单独发布。发布的产物下发和线上IOS/Android进行隔离,保证测试上线阶段,不影响已经上架的IOS/Android应用。
待后续鸿蒙应用稳定,再支持一键同时发布IOS/Android/HarmonyOS Next平台。
又思量到业务场景存在一套代码,跨RN版本发布。发布体系改造,支持了发布时根据发布单选择的RN版本,自动选择依赖配置进行打包发布。提升业务发布效率。
5)在监控方面,实现鸿蒙端的监控数据上报,接入到现有的监控体系,方便线上监控。
6)在排障方面,实现鸿蒙端的异常数据上报,接入现有排障体系,方便线上排障。
3.1.3 业务工程改造
1)业务方按照提供升级文档和工具进行具体业务工程改造。
2)升级改造后,进行本地开辟环境测试,发现问题,办理问题。
3)本地测试通事后,进行打包发布,进入集成测试阶段。
在升级过程中,工作量最大的部分是“RN自定义组件和API实现新架构改造”。
这里先先容下RN新架构。RN新架构是指从0.68版本开始后的架构。主要包括:
- Turo Modules 模块体系,更换老架构中的Native Modules,用于JS到Native的API同步调用。
- Farbic 组件体系,更换老架构中Native Component,支持同步渲染。
由于鸿蒙RN只支持新架构,所以必要将RN自定义组件和API实现进行新架构改造。在携程旅行App中,我们使用有100+的自定义组件和API。这部分的改造工作量非常大,建议在做适配时优先处理这部分工作。
3.2 差异化工作
在RN版本升级到0.72.5后,开始鸿蒙端特有的适配。
鸿蒙RN框架特点:
- 已实现了官方RN大部分组件、API
- 已实现社区常用的三方库
- 自定义组件和API必要应用开辟自行实现
差异化工作:
1)自定义组件和API实现
- 100+自定义组件和API,基于鸿蒙原生开辟实现,再封装提供给RN调用
- 按优先级分阶段实现这些自定义组件和API,保持上层JS接口不变
2)RN工程改造
- 添加react-native-harmony和react-native-harmony-cli依赖库
- 适配Platform.OS,Platform.select等API
- 实现xxx.harmony.js文件,逻辑与IOS保持同等
- 升级三方库版本,如react-native-gesture-handler,从1.X版本升级到2.X版本
- 三方库版本升级后,对不兼容的地方做适配
3.3 原生组件开辟
携程CRN框架经过近8年的迭代,业务线非常复杂,自定义的组件、turboModule有100多个。
在鸿蒙中适配CRN,首先面临的工作就是将这些自定义组件、turboModule在鸿蒙原生端用ArkTS重新实现。
我们面临以下几个挑战:
1)工作量
这些组件经过了近8年的迭代,开辟负责人可能几经易手。有些复杂组件,如信息流组件、自定义地图、日历组件、多媒体组件等,逻辑异常复杂,经过跟原开辟负责人、产品等初步讨论,工作量都凌驾单人一个半月。而我们面临的是100多个组件、turboModule的重实现。
2)HarmonyOS Next渐渐完善,与Android、iOS在某些特性上有差异
开辟过程中发现了很多HarmonyOS Next功能不完善、存在若干Bug的地方,毕竟是一个新体系,我们与华为同学紧密合作,一一办理了问题,这个过程见证了鸿蒙体系的愈发成熟。
出于安全思量,鸿蒙体系有一些新特性,比如选取图片视频进行编辑的场景,在Android、iOS中,申请用户权限之后便可以拿到整个体系相册的图片视频,这确实可能存在一些安全隐患。鸿蒙在最开始就切割了这一操作,即使App经用户同意申请了读相册权限,也无法拿到体系主相册的图片视频,本意是让App直接跳到体系相册选取图片之后返回,只提供当次选中的图片信息给App,从而彻底断绝了App侵犯用户隐私的可能。
但我们的多媒体场景比力复杂,用户选取图片、视频后会跳入编辑页,且可以重回相册页选择其他图片,也就是说我们的图片视频选择页与编辑页存在联动,鸿蒙提供的这种跳入体系相册的方式体现无法满意我们的需求。
后续经过讨论,鸿蒙提供了相册Picker的方案,将体系相册页封装为组件提供给开辟者,我们的图片视频选择页可以内嵌相册Picker,从而办理了联动的问题。但这个需求从开始评审、开辟、测试到最终实现,花费了几个月的时间。
3)RN组件C化
在接入RN的过程中,发现鸿蒙中RN关键性能指标与Android、iOS有差距,华为鸿蒙RN团队为了办理性能问题,提出了组件C化的方案。
简单来讲,就是将ArkTS实现的组件用C-Api重新实现一遍,华为方面给出的要求是容器结点(RN代码中存在标签<></>嵌套的组件)需逼迫C化。虽然携程中这种必须C化的组件并不多,但也带来了非常多的适配工作。具体可参考下篇-组件C化。
部分组件图如下:
Fabric、TurboModule
最开始,我们在实现相关Fabric、TurboModule的时候,鸿蒙RN框架还没有提供Spec文件CodeGen工具,端赖手写。不外如今已经提供了相关工具,具体操作步调可以参考相关文档。
Spec文件天生之后,剩下的工作就是相关组件、TurboModule的功能桥接实现,逻辑较为简单,实现相关功能就好。
必要注意的是:
- RN代码中存在标签<></>嵌套的组件被视为容器结点,此类型组件需使用C-API实现。
- 可以通过this.ctx获取RNOHContext,进而获取RNInstance,从而获取一系列RN端JS传入的信息,如View宽高、style等,也可实行发送事件、接收事件、获取TurboModule进行其他操作等等。
- 在RNInstanceImpl构造函数中有一个arkTsComponentNames字段,可以传入全部我们自定义叶子结点Fabric组件的名称,用于在RNOH SDK内部进行指令分发优化。实现ArkTS端Fabric组件后,必要将Fabric组件的名称加入此列表中。
- 假设存在实现过于复杂大概其他原因无法C化的容器组件,RNOH SDK内部指令优化代码需修改(这也意味着RNOH SDK需重新打包编译),关键代码见下文‘性能优化-5.3 RN 指令精简章节。
3.4 组件C化
经过与华为的详细沟通,RN代码中存在标签<></>嵌套的组件被视为容器结点,此类型组件需逼迫C化。
也就是此类型的组件:
- <RNComponent>
- <Text/>
- <Image/>
- </RNComponent>
复制代码 RNComponent算为容器结点
经确认,携程端存在四个需逼迫C化的容器结点组件,分别为:
名称
| 描述
| SwipeoutView
| 可滑动组件
| ScrollView
| 滚动组件
| CustomScrollView
| 自定义列表组件
| CRNModal
| modal容器
| 简而言之,必要把ArkTS端实现的组件用C-Api再次实现。
3.4.1 CRNModal C化
CRNModal 在开辟测试过程中一步步探索了实现方案,经过多轮测试、方案讨论调解,最终确定了C化方案。
方案一:尝试使用体系Modal实现这个组件
后续测试过程中发现体系Modal的实现方案为体系Dialog,层级很高,携程业务线会出现这样一种场景,RN页面打开Modal后点击跳转一个其他页面,新打开的页面会出如今Modal的下方,不符合需求,方案镌汰。
方案二:尝试通过新跳转一个透明页面的方式实现modal
测试发现新跳转一个页面后,RN的点击事件分发出现问题,无法响应任何事件。且我们App的路由方案为Navigation,经与华为方面沟通,Navigation C化难度非常巨大,短时间不可行。此方案镌汰。
方案三:尝试在RN JS端创建modal
JS端创建一个style为position: 'absolute', zIndex: 999的容器,层级提高,体如今其他组件上方来实现modal。测试发现调用体现Modal的地方很多,可能会在一个嵌套很深的层级中,如果在这里尝试展示这个zIndex: 999的容器,还是会有被遮挡的环境,最终此方案也被镌汰。
方案四:C++层进行插入
经过内部讨论,这个modal应该展示在整个RN页面层级的最上方,这在Android、iOS中都很好实现,但鸿蒙是一个声明式的语言,无法拿到页面实例,无法拿到父组件,也就无法进行插入。
但研究RNOH SDK之后发现,C化后的RNInstance实例在C++端持有一个XComponentSurface,全部RN页面临应的Native组件都被添加体如今这里,而XComponentSurface可以获取rootView实例ComponentInstance,这是一个根控件,将这个根控件强转为ViewComponentInstance之后,在ViewComponentInstance.cpp代码中可以类似Android,获取childCount,通过index添加child等等,进而可以实如今RN页面层级最上方添加modal,最终也是依据此方案,实现了CRNModal组件。
流程如下:
3.4.2 开辟注意事项
1)在CAPI instance中声明的Node节点,必须在全局声明,否则会导致node节点不能收到node_event等消息;
2)设置node属性构建ArkUI_AttributeItem的时候,如果设置的值是一个ArkUI_NumberValue类型,必要指定size,这个size的盘算必须除去类型的长度,如下:
- ArkUI\_NumberValue value\[] = {{.i32 = alignItem}};
- ArkUI\_AttributeItem item = {value, sizeof(value) / sizeof(ArkUI\_NumberValue)};
复制代码 3)animateTo执举措画,在组件析构之后还是会回调,必要控制好生命周期避免crash;
4)设置Stack背景,导致子组件布局错误,是因为Stack被作为同级组件从而导致子组件的postion参数异常,必要手动处理好position问题;
5)可以通过以下方式在C++层调用arkTS方法,获取相关数据:
方案1:在ArkTS里实现一个TurboModule方法,然后通过rnInstance->getTurboModule<XXTurboModule>获取对应的TurboModule,调用方法,获取返回值。但此方案涉及C++与ArkTS的跨端调用,性能会差一些,优点是实现简单。
方案2:通过ArkTSBridge,添加一个ArkTS方法的桥,然后就可以在C++里直接调用这个ArkTS方法。具体实现可以参考NapiBridget.ArkTSBridgeHandler里恣意方法。此方案性能好,但实现起来稍微贫困一点。
6)可以通过以下Api获取装备的高宽
- auto displayMetrics = ArkTSBridge::getInstance()->getDisplayMetrics();
- displayMetrics.screenPhysicalPixels.width / displayMetrics.screenPhysicalPixels.scale //直接获取到是px单位,需要进行转换,也可以自行修改TurboModle的初始化值:
复制代码 四、遇到的问题息争决办法
在升级适配过程中,我们遇到了一些RN新架构问题,还有一些鸿蒙RN特有的问题。
RN 新架构问题:
- IOS Animated.timing 设置 useNativeDriver:true 后,内嵌按钮无法点击
- IOS TouchableOpacity 内嵌 Aminated.View ,Aminated.View 开启动画变动位置后,无法点击
- IOS Image样式设置 borderRadius 体现不全
- IOS minimumFontScale maxFontSizeMultiplier 不生效
- Aminated.View 内嵌Modal组件,内部TouchableOpacity点击不响应
- FlatList、ScrollView stickyHeaderIndices 吸顶功能多次滑动后失效
- Aminated.View 、Animated.ScrollView、layoutAnimation 动画卡顿
- 样式中使用了zIndex属性层级可能不生效,尝试添加 position:relative属性后生效
- 组件必要设置默认高宽,否则布局展示可能发生截断
由于动画、样式、性能影响较大,最终决定在RN 0.72.5版本(iOS/Andriod)中只使用Turbo Modules,不开启Fabric模式,来规避掉这些问题。
但鸿蒙RN只支持新架构,新架构存在问题有些在鸿蒙端同样存在。我们和华为搭档紧密沟通来处理这些问题。对于无法规避问题只能业务侧做兼容处理。
鸿蒙RN特有的问题:
问题:RN Modal弹窗体现时,再打开一个H5页面会体如今Modal下面
办理办法:实现一个View层级的CRNModal替换RN Modal
问题:绝对定位中添加top:“auto”导致元素不体现
办理办法:去除top:“auto”设置
问题:zIndex:-1元素不体现
办理办法:在最外层的View添加collapsable={false}属性
问题:position:absolute样式漂移
办理办法:在外层的View添加collapsable={true}属性
问题:react-native-harmony/metro.config 和现有的自定义metro配置辩论
办理办法:提取react-native-harmony/metro.config中harmony平台相关处理,归并到自定义metro插件中
五、性能优化
华为内部对鸿蒙体系寄予厚望,为了追求更好的用户体验,盼望鸿蒙APP核心业务场景性能指标达成业内最佳水平。对携程来说,大多数业务页面都是RN,RN技术栈对性能指标非常敏感,很小的性能优化或劣化,都会大幅影响用户体验。
5.1 CRN预加载
默认环境下,我们会在页面的生命周期中去加载rn_bundle,因为页面已经进入生命周期开始展示了,加载bundle又会有一定的耗时,这种环境下,就会产生白屏征象。
携程也存在某些页面依赖接口数据且接口返回比力慢的环境,比如机票列表页,在进入页面白屏之后又会有长时间的骨架屏,用户体验差。
经过调研,携程端基于体系的FrameNode本领,实现了CRN预加载方案,办理了上述问题。
5.1.1 FrameNode
鸿蒙中FrameNode是一个非常强大的本领,不光是各大厂商在用,官方ArkUI中也大量使用了FrameNode进行性能优化。
它的特点用一句话可以描述:背景离屏渲染,前台上树展示。
使用这个特点,可以实现组件渲染与页面展示的完全分离。也就是说组件的创建渲染不再依赖页面的生命周期,这样我们就可以做很多事情了。
但正因为FrameNode组件会在背景真实渲染,它使用起来会有一定的风险,背景渲染的组件可能会影响前台行为,比如改变状态栏颜色、弹Toast、弹Dialog等,这些都必要人为进行规避。
在RNSDK中我们对Toast、Dialog、状态栏等行为的TurboModule调用,根据页面状态进行了拦截,页面不可见时,上述这些TurboModule的调用都不会生效,而且会记载最后一次拦截的行为及参数,在页面变为可见时,恢复最后一次被拦截的行为。
基于FrameNode本领,实现了鸿蒙中CRN预加载的1.0和2.0方案,下文会详细先容这两个方案。
另外必要注意的一点是,携程在RN中使用FrameNode过程中,遇过一个困扰许久的问题:使用FrameNode加载RN页面时,在某些比力复杂的页面,会发生非常严重的JS壅闭征象,用户的点击、返回等操作行为被页面渲问鼎令壅闭,迟迟得不到响应,极度影响用户体验。
经过与华为方面的团结排查,发现是因为RNInstance初始化时传入的参数:disableConcurrentRoot=true导致。此参数会关闭React18一个性能优化的功能:微任务指令批量提交,从而导致在JS代码setTimeout中进行setState时,指令立刻提交,总指令数大幅增加,进而大幅影响RN指令处理效率。
大家如果也会在项目中用到FrameNode进行RN页面的性能优化,在初始化RNInstance时,disableConcurrentRoot参数一定要传false。
5.1.2 CRN分包
要理解我们CRN的预加载方案,首先要了解我们的分包逻辑,具体可参考文章:《近万字长文详述携程大规模应用RN的工程化实践》。
总体而言,将业务bundle的加载分为两部分: rn_common & rn_business。其中rn_common包含完整的底子框架本领,rn_business则是具体的业务逻辑代码。通过nativeRequire的方式,分行加载rn_business中的业务代码,然后在加载了rn_common的空白页面上进行渲染。
可以发现,CRN这种分包模式完美契合FrameNode,我们使用FrameNode预渲染一个加载了rn_common的空白页面,这个空白页面不会渲染UI元素且具备完整的框架本领,不会有任何影响前台页面的行为,等到页面真正展示时,才去加载业务代码rn_business,进行UI渲染,从而完美规避FrameNode的使用风险。
也正是基于此,我们实现了CRN预加载1.0的方案。
5.1.3 CRN预加载1.0
预加载1.0方案:
- 在前置页面通过FrameNode预加载一个RNSurface,使用这个RNSurface去加载rn_common,完成后可以理解为背景存在了一个具备全部框架本领的空白页面。
- 用户点击跳转RN页面时,添加一个用户险些不可感知的延时去加载rn_business。
- 充分使用这个跳转延时 + 页面创建 + 页面切换的动画时间去加载业务bundle、渲染等。
- 业务Bundle加载完成后,动态更换业务自定义的intialProps
- 做到了rn bundle加载、 渲染与页面生命周期的完全隔离。
- 目前预加载1.0方案在全业务默认使用,基本办理了RN页面首帧白屏问题。
在携程的某些业务线中,页面UI依赖网络接口数据,且受外部接口影响,响应较慢。这时候,打开页面会有较长时间的骨架屏loading,也非常影响用户体验。
如果在前置页面中,我们可以大概率猜到用户下一步跳入的目的页面,那是不是可以使用FrameNode将目的页面提前加载,且根据前置页面的参数进举措态刷新,这样用户真正跳入目的页面的时候,就可以直接上屏,达到秒开的结果。
基于此,我们实现了CRN预加载2.0。
5.1.4 CRN预加载2.0
预加载2.0方案:
- 在前置页面通过FrameNode预加载了一个真实的RN页面,完成了加载rn_commom、rn_business、接口请求、渲染等一系列流程。
- 前置页面中影响下一个页面关键参数发生改变时,发消息给背景预加载的的RN页面,RN页面接收到事件,拿到关键参数后进行网络请求,得到数据后对页面进行刷新。
- 用户点击跳转到目的页面时,直接将背景已经预渲染好的页面上屏展示。
- 因为页面已经在背景被真实渲染,有影响前置页面的风险,虽然我们在RN SDK层面已经做了一层拦截,但这种拦截不可能cover全部场景,全部接入了预加载2.0方案的业务都必须在上线前经过完整回归测试。
- 目前,我们在机票列表页及火车票详情页使用了预加载2.0方案。
对比视频:
性能优化关闭:
性能优化开启:
5.2 RN TurboModule运行在Worker线程
前段时间我们在RN JS端对TurboModule调用加了一个埋点,统计TurboModule方法调用的耗时,后续也是根据这个埋点天生了一个报表,发如今鸿蒙中,TurboModule同步方法调用耗时比Android、iOS耗时长10倍,某些方法甚至慢100倍。
经过分析,在Android、iOS中TurboModule都是运行在单独的子线程中,而在鸿蒙中,TurboModule都运行在主线程,主线程要承载一些别的任务比如页面渲染、用户操作行为响应等,这些行为会导致鸿蒙中TurboModule的调用被壅闭,耗时就长。比如下图的Trace,如果TurboModule在UI线程运行,那就可能会被壅闭,壅闭的这段时间,js线程只能等待,而这段等待是毫偶然义的。
前段时间,鸿蒙RN SDK也是加入了TurboModule运行在Worker线程这个本领。RNInstance在创建时会同步创建一个worker线程,专门用于TurboModule运行。我们要做的是对工程中TurboModule代码进行适配改造,使之可以运行到worker线程中。
整个适配过程也存在一系列的问题。
首先,鸿蒙的ArkTS衍生自TS语言,基于Actor线程模子,内存不共享,线程间数据通信非常贫困。
为了办理线程间通信流程繁琐的问题,鸿蒙提供了Sendable注解,可以理解为被这个注解修饰的对象会在共享内存创建。但Sendable存在一个问题,Sendable对象的成员变量只能是Sendable对象或其他特定的数据类型,也就是说我们如果对一个对象进行Sendable改造,就必须对他的全部成员变量进行Sendable改造,也必要对成员变量的成员变量进行Sendable改造,那这个改造过程就存在指数级扩散的问题。
另外,Sendable注解提供的时候,我们大部分代码都已经完成了,在这种成熟的大型项目中再重新进行Sendable改造的成本非常高,大家各自App如果还没开始大概刚开始开辟,一定要思量Sendable适配的问题,比如数据类型默认使用collection下属map、array,class默认添加Sendable注解等。
目前,我们适配完成了7个TurboModule,其他TurboModule做Sendable适配的成本非常高,正在渐渐进行中。
5.3 RN 指令精简
在鸿蒙中,RN支持两套组件:C-API实现的组件以及原生ArkTS实现的组件。
C-API实现的组件性能更好,华为的支持力度更大,出于性能思量,大多数厂商使用RN时,都会选择C-API实现的组件。
C-API组件的Create、Insert、Update、Remove等指令不再必要传递给ArkTS侧。仅仅几个自定义的ArkTS组件必要将指令传递给ArkTS侧。如下图中绿色节点的指令。
鸿蒙RNOH SDK中有默认算法,可以保留叶子节点ArkTS组件的指令,如果项目内没有ArkTS容器组件,RNInstance初始化时添加配置arkTsComponentNames就好。
但在携程的业务中存在AdatpterMap这个容器组件,依赖体系花瓣地图,这个地图C化难度巨大,所以我们的AdatpterMap暂时也只能由ArkTS实现。
这就必要我们自己设盘算法。在保证性能的环境下,完整保留AdatpterMap及其子组件的相关指令。
以下是关键代码:
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
- }
- }
- }
- //第三次遍历:根据2中齐全的tag,重新过滤所有指令,保留这些tag的create、insert、update、remove指令。
- for (auto mutation : mutations) {
- ...
- //根据组件Tag,保留需要传递给ArkTS的所有指令
- }
- return validMutations;
- }
复制代码 再来看下成果,测试RN页面中,算法过滤的不必要传递到ArkTS侧的指令数凌驾99.9%。
页面
| 优化前指令数
| 优化后指令数
| 旅店首页
| 3092
| 11
| 旅店套餐
| 2181
| 0
| 机票+旅店
| 496
| 2
| 旅店
| 3838
| 3
| 美食/购物
| 1542
| 3
| 5.4 分帧渲染
分帧渲染主要用在App的启动优化中。首页宫格存在两屏,二屏在刚开始是不可见的,可以在首页加载完毕之后再去加载宫格二屏。
分帧渲染可以监听到帧渲染的回调,这样就可以对页面元素的加载优先级进行定制,将告急的元素优先加载,不告急大概不可见的元素后续加载。进而提升页面的性能。
关键代码:
- 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() {
- //默认加载宫格首屏
- if (this.stages > 0){
- this.genFirstCell(0)
- }
- //三个渲染帧之后,加载宫格二屏
- if (this.stages > 2){
- this.genFirstCell(1)
- }
- }
- ...
- }
复制代码 接入分帧渲染,控制宫格二屏的渲染机遇后,首页的启动耗时减少了20ms。
5.5 后续性能优化
华为鸿蒙RN团队规划有一个性能优化的feature,在这里简单先容下。
5.5.1 更换RN JS实行引擎:JSVM(基于V8)
JSVM相较hermers,预计可以提升20%的JS解析性能。前段时间华为提供了一个rn sdk,我们新建了一个分支验证了一下这个JSVM,js的加载速率确实比hermes要快一些。但RN产物jsbundle的加载比hermes要慢,这意味着页面的首屏性能会受到一定影响。这是我们非常关注的一个性能指标,问题得到办理之后,我们也会进行切换。
六、成果和未来规划
经过4个月鸿蒙版本的开辟和适配,2024年6月18日携程在鸿蒙应用商店上架了首个全业务全场景的携程旅行鸿蒙版应用。业务方在Android/iOS上的一套CRN代码,只需经过简单的适配,就能正常在鸿蒙体系上运行,甚至有些业务不必要修改,现有的代码直接在鸿蒙体系上能完整的跑完业务流程。
未来,我们还会在以下两个方面一连对鸿蒙CRN框架进行优化:
用户体验
用户体验和性能一直是我们关注的重点,CRN在鸿蒙体系上还有很大的优化空间。我们会一连在性能上继续打磨和提升。
技术布局
为了追求高效率、低成本的研发模式,未来携程业务开辟会大量使用一码多端的框架xTaro。后续xTaro会支持鸿蒙体系,真正实现让业务的一套代码能在多端多平台多应用场景上全矩阵运行。
鸿蒙生态的发展是一个一连且快速的过程。随着鸿蒙体系的不断迭代升级和生态的渐渐完善,我们会一连为用户提供更加智能、安全、便捷的一站式的旅行应用。
【推荐阅读】
- 携程弱网辨认技术探索
- 携程旅店同一云手机平台探索与实践
- 瘦身50%-70%,携程 Taro 小程序样式 Size 缩减方案
- 通过及时调试,让AI编写有效的UI自动化
“携程技术”公众号
分享,交流,发展
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |