HarmonyOS:应用性能优化实践
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤博客园地址:为敢技术(https://www.cnblogs.com/strengthen/ )
➤GitHub地址:https://github.com/strengthen
➤原文地址:https://www.cnblogs.com/strengthen/p/18515687
➤如果链接不是为敢技术的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
2、使用AOT模式对应用进行编译优化:方舟编译运行时通过采用PGO(Profile-Guided-Optimization,配置文件引导型优化)方式,提前生成高性能机器码,从而提升程序运行速度。
3、ArkTS是基于TypeScript设it的,但出于代码的稳定性和性能考虑,一些TypeScript的特性被限制了。比如需要不支持属性的动态变更、变量或参数需要有明确的类型声明和返回值声明等。
4、严格遵循ArkTS的编码规则,禁用 @ts-ignore @ts-expect-error等屏蔽编译校验的命令,这些命令规避了系统的编译校验,容易引起稳定性问题。
5、开启TypeScript的严格模式,比如需要严格判空、严格函数类型检查、严格成员初始化等,提高代码的质量和可维护性,避免一些常见错误。扩展:ArkTS在设计这些规则的时候,主要是考虑到语言的静态性,就是尽量在运行时保留属性、类和成员变量等等一个具体的类型,只有保留了这些具体的类型,并且进行严格模式之后,可以提高代码的可维护性,可以在工具链进行编译或者引擎在运行具体的ArkTS源码时,可以做一些运行时的优化,提升运行时性能和引擎的运行性能。
6、ArkTS不支持any和unknown:不支持使用any和unknown,请使用明确类型,或者使用联合类型、泛型或者object替代any,必须使用any的场景可以使用ESObject代替,ESObject是ArkTS和TS语言跨语言的调用过程,是可能存在性能问题,谨慎使用。
7、AOT (Ahead Of Time)即提前编译,能够在 Host 端将字节码提前编译成 Target 端可运行的机器码,这样字节码可以获得充分编译优化,放到 Target 端运行时可以获得加速。开启AOT为什么能够使端侧直接去执行机器码?其实是有一个从字节码生成机器码的过程,在端侧应用运行的过程中,开启了AOT的指令,会生成一个AP文件,这个AP文件里面包含了应用在运行过程中的一个热点函数,把这个AP文件打包到host端,即编译端,在编译端过程中,会去读取这个AP文件,从中获取热点函数的信息,根据这个信息把原有的字节码生成对应的机器码,再把对应的机器码和字节码一起打包成一个hap包,安装到客户端侧。所以客户端侧就拥有了热点函数的机器码,当引擎去运行对应的代码命令的时候,即运行到热点函数的时候,就可以直接执行机器码。从而节约了从字节码转换成机器码的过程,从而提升了运行速度和性能。
1、冷启动过程简介:应用启动时,后台无该应用的进程,需要创建新的进程,这种启动方式叫做冷启动。
(1)、应用冷启动的过程:应用启动时,后台无该应用的进程,需要创建新的进程,这种启动方式叫做冷启动,冷启动的过程大致可分成下面四个阶段:应用进程创建和初始化、App和Ability的初始化、Ability生命周期、加载绘制首页。
(2)、提升冷启动的方法:
第一阶段:缩短应用进程创建&初始化阶段耗时:设置合适分辦率的startWindowlcon,即启动图标,需要合理设置启动图标的大小,因为在应用启动的时候,需要去解析这个图标的数据。
第二阶段:缩短Application&Ability初始化阶段耗时:减少首页Ability或者Page中import的模块。即适当的减少应用首页必须要依赖的模块,有些模块可以放到第二页或者第三页去初始化。像有些不是首帧显示必须要依赖的流程和模块,可以使用动态加载的方式或者延迟加载的方式。
第三阶段:缩短Ability生命周期阶段耗时:使用异步加载。比如在aboutToAppear()的时候,做一些页面相关的必须信息的初始化,如果是同步操作,可能就会阻塞页面的生命周期,此时可以使用异步的方式去替代同步的方式,避免阻塞页面的生命周期。
第四阶段:缩短加载绘制首页阶段耗时:延迟加载。比如使用LazyForEach懒加载的形式,减少首页数据的加载,消除冗余加载,提升首帧绘制的速度,
2、使用异步加载:使用异步加载可以在后台线程中处理耗时操作,从而提升应用响应速度。生命周期中如果必须要运行耗时操作,可以考虑使用异步接口或者异步加载的方式,延迟耗时操作的运行时机,提升应用的启动速度。
使用动态加载:页面在引入依赖的加载模块时,如果不是首帧必须显示的内容模块,可以使用动态加载的方式(await import),延迟模块的加载时间,加快首帧显示的加载速度。
3、延迟加载:将不必要的资源延迟加载可以减少应用启动时间。使用List、Grid以及Swiper等容器组件时,配合系统提供的LazyForEach数据懒加载能力,可以有效减少应用启动时间和内存占用。在长列表场景中,推荐开发者使用LazyForEach替代ForEach。LazyForEach是一种懒加载的实现模式,能够显著提升列表页面的加载速度。
4、使用缓存:选择合适的缓存策略可以提高应用程序的性能和响应速度,从而提升应用响应速度。当使用LazyForEach时,我们可以搭配使用cachedCount方法,自定义控制列表的缓存数量,实现更优的滑动体验。当cachedCount不设置的时候,其会默认加载一条缓存数量。应该根据实际业务场景去合理的设置cachedCount,以达到性能和体验在一个比较均衡适宜的状态。
1、避免在主线程上执行耗时操作:UI主线程是HarmonyOS应用中最重要的线程之一,在主线程上执行耗时操作会阻塞U\渲染,从而导致UI主线程的负载过高。因此,可以将耗时操作放在TaskPoo或者Worker等后台线程中执行。UI生线程是应用中最重要的线程之一,在主线程上执行耗时操作会阻塞U渲染,从而导致U主线程的负载过高。因此,可以将耗时操作放在TaskPool或者Worker等后台线程中执行。可以从上图的几组数据中看出,把耗时操作放在子线程可以显著降低丢帧率,提升应用的流畅度。把耗时操作放在子线程中去执行,再把执行的结果返回到主线程中去。
2、 减少渲染进程的冗余开销:使用资源图代替绘制、合理使用renderGroup、尺寸位置设置尽量使用整数,可以减少渲染所需的时间,从而减少丢帧卡顿。
组件转场动画推荐使用transition,组件转场动画指在组件出现和消失时做动画,有两种实现方式:
(1)、属性动画+动画结束回调逻辑处理,即animateTo(不推荐)。
(2)、组件转场动画transition(推荐)。transition实现方式就是系统设计出来,为了组件转场动画的使用,转场动画效果实现起来会更加的简单,代码量更少。transition的性能会比animateTo性能更好。因为animateTo其实是一个动画播放的过程,先去对比当前的状态,然后对比完之后,去更新当前状态和过去状态的区别点,即它的脏区,animateTo需要去判断初始状态和结束状态两个状态,需要进行两次更新,而transition只需要去判断初始状态,或者是结束状态的最终的一个结果,transition只需要做一次判断更新,
合理使用动效一动画参数相同时,使用同一个animateTo,由于每次animateTo都需要进行动画前后的对比,使用多个animateTo的性能就不如只使用一个animateTo。特别是针对设置在同一个组件的属性,能减少该组件更新的次数。
合理使用动效一多次animateTo时统一更新状态变量:animateTo会将执行动画闭包前后的状态进行对比,对差异部分进行动画。为了对比,会在执行animateTo的动画闭包之前,将所有变更的状态变量和脏节点都刷新。如果多个animateTo之间存在状态更新,会导致执行下一个animateTo之前又存在需要更新的脏节点,可能造成冗余更新。建议开发者:在一个animateTo到另外一个animateTo进行播放的过程中,中间尽量避免做状态变量的改变。如果需要去做状态变量改变,要把它放在这个动画播放之后,或者是播放完成后的回调里实现。
合理使用RenderGroup:还有一种提升动画效果的方式,使用RenderGroup。首次绘制组件时,若组件被标记为启用renderGroup状态,将对组件以及其子节点进行离屏绘制,将绘制结果进行缓存,此后当需要重新绘制组件时,就会优先使用缓存而不必重新执行绘制了,从而降低绘制负载,优化渲染性能。
当被标记了,renderGroup方法为true的这个组件的时候,系统会把这一整个组件绘制结果给缓存起来,当这个绘制结果做一些属性的变更动画,比如平移、旋转或者大小的变化,透明度变化等一系列属性变化的时候,就可以使用这里面缓存的内容做一些矩阵的变换,不需要重新再去绘制原有的组件。对一个固定的没有状态变量绑定的组件去做一些属性动画的时候,可以加上这个renderGroup属性去加速它这个组件的绘制过程,前提就是说组件的内容是不会发生变化的,没有绑定任何的状态变量,因为发生变化之后,缓存的信息就失效了。
3、减少视图嵌套层级:应用开发中的用户界面(UI)布局是用户与应用程序交互的关键部分。不合理的布局越多,视图的创建、布局、渲染等流程所需的时间就越长。因此,减少嵌套层次或者使用高性能布局节点,可以减少丢帧卡顿。
尽量减少嵌套层级,嵌套层级越深,系统的内存开销越大。因此在开发过程中,要尽可能減少布局嵌套,使布局更加扁平化。例如RelativeContainer可以根据锚点来进行低嵌套层级复杂布局,而List和Grid等高级组件不但能使布局扁平化,而且支持懒加载等提升性能的方法,是更加推荐的布局方式。
4、组件复用配合LazyForEach:使用ArkUI开发范式提供的组件复用机制,通过重复利用已经创建过并缓存的组件对象,降低组件短时间内频繁创建和销毁的开销,提升组件加载效率,降低U线程负载,从而减少丢帧卡顿。@Reusable标记组件复用,还需要去实现aboutToReuse方法。
推荐场景:
(1)、滑动场景下对同一类自定义组件的实例进行频繁的创建与销毁(配合LazyForeEach);
(2)、反复切换条件渲染的控制分支,且控制分支中的组件子树结构相同;即组件频繁的创建和销毁的过程,这个过程也可以使用组件复用去实现。
列表中的组件复用机制如下:
(1)、标记为@Reusable的组件从组件树上被移除时,组件和其对应的ISView对象都会被放入复用缓存中。
(2)、当列表滑动新的ListItem将要被显示,List组件树上需要新建节点时,将会从复用缓存中查找可复用的组件节点。
(3)、找到可复用节点并对其进行更新后添加到组件树中。从而节省了组件节点和JSView对象的创建时间。
5、精确控制状态变量的关联组件数:@state等状态变量关联组件越多,状态数据变更时刷新的组件越多,U线程负载越重,因此移除冗余的组件关联可以减少丢帧卡顿。不推荐使用更新单个状态变量的形式自行控制多个组件更新时机(命令式);推荐使用状态变量和组件—一绑定的方式,以数据的变更驱动组件的刷新(声明式);
合理控制状态更新范围,避免关联刷新较大范围或者渲染较慢的组件。
6、在对象上谨慎使用状态变量关联
理解@Prop和@objectLink的区别:@Prop是深拷贝,@ObjectLink是浅拷贝。所以在@Prop和@ObjectLink使用效果相同的场景下,优先使用@objectLink的方式减少系统内存开销。
2、Launch Insight:录制和还原从启动应用,到显示首帧过程中的cPU、内存等资源使用情况,用于分析启动耗时长的问题。
3、Frame Insight:录制卡顿过程中的关键数据,标注出应用侧、RenderService 侧卡顿帧,用于分析应用卡顿、丢帧的问题。
4、Time Insight:通过周期性采集调用栈,识别CPU耗时高的热点代码段,用于分析卡顿、CPU 占用高、运行速度慢等问题。
5、Allocation Insight:录制和分析内存分配记录,用于分析内存峰值高,内存泄漏,内存不足导致应用被强杀等问题。
6、Snapshot Insight:录制和分析应用程序中 ArkTS对象的分布,通过快照方式对比 ArkTs 对象分布区别,用于分析内存泄漏问题。
7、CPU Insight:录制cPU 调度事件、线程运行状态、CPU 核频率、Trace 等数据,可用于分析卡顿、运行速度慢、应用无响应等问题。
8、Smart Perf:开源性能调优平台,支持对CPU调度、频点、进程线程时间片、堆内存、帧率等数据进行采集和展示,展示方式为泳道图、火焰图等。