1. HarmonyOs提升应用启动和响应速度
着手点
-
使用AOT模式
AOT(Ahead of Time)提前编译,能够在Host端将字节码提前编译成Target端可运行的机器码,这样字节码可以获得充分编译优化,放到Target端运行时可以获得加速。
-
提升应用启动和响应速度
-
使用异步加载:使用异步加载可以在后台线程中处理耗时操作,从而提升应用响应速度
-
延迟加载:使用List,Grid以及Swiper等容器组件时,配合系统提供的LazyForEach数据懒加载能力,可以有效减少应用启动时间和内存占用。
-
使用缓存:选择合适的缓存策略可以提高应用程序的性能和响应速度,从而提升应用响应速度。
-
-
应用冷启动时优化
应用第一次启动时,即后台无该应用需要创建新的进程。冷启动过程大致可分成四个阶段:
-
应用进程创建和初始化:设置合适分辨率的startWindowIcon(启动图标)
-
App和Ability的初始化:减少首页Ability或者Page中import的模块
-
Ability生命周期:使用异步加载机制
-
加载绘制首页(页面和组件的生命周期):延迟加载
-
合理使用布局
在使用ArkUI开发中,我们通过布局组件和基础组件进行界面描述,这些描述会呈现出一个组件树的结构,基础组件在其中为叶子结点,布局组件则是中间节点,可以把这棵树称之为应用组件树。当用户执行交互(滑动,点击等行为)时会触发界面修改,界面的修改本质上是通过触发这棵组件树的重新渲染,来实现应用界面更新的过程。
1、数据处理过程中主要是对状态数据进行更新,状态数据指得是所定义的@State等相关的数据。这部分包含数据本身的更新时的性能,并且也影响与状态相关的组件数量,影响下一步的更新性能,那么对于开发过程需要关注的,就是避免无效的数据更新导致冗余的UI更新操作。
2、UI更新过程中则是对需要更新的元素进行更新操作,对应的元素会经历Build、Measure、Layout和Render等阶段。其中Build是执行组件创建和组件标脏的过程,Measure是对组件的宽高进行测量的阶段,Layout是对元素进行在屏幕上位置进行摆放的阶段,而Render则是根据测量和布局得到的大小位置等信息,进行提交绘制的过程。
1.精简节点数
布局阶段是采用递归遍历所有节点的方式进行组件位置和大小的计算, 如果嵌套层级过深,将带来了更多的中间节点,在布局测算阶段下,额外的节点数将导致更多的计算过程,造成性能消耗。
组件平铺和嵌套在相同组件个数的情况下,其性能差异不大,并且整体上趋势保持一致,随着组件数量增加呈现线性增长的劣化,由此可以得到结论,真正影响布局性能的因素是参与布局的节点数量。
-
移除冗余点
//冗余 Row() { Row(){ Image() Text() } Image() } //优化后 Row() { Image() Text() Image() }
-
使用扁平化布局减少节点数
-
页面创建时,扁平化减少了中间的嵌套层级,使总的组件节点的数量越少,在进行布局时所需要进行的计算相对越少。
-
页面更新时,当要更新的结构是嵌套子树的结构,其树内包含过多节点时,整体更新会导致更新的节点数消耗布局性能。
所以当页面不存在冗余节点时,可以考虑是否能够通过替换为更高级的布局使得页面扁平化,来达到减少节点数的目的。主要方式可以参考:
- 通过相对布局实现扁平化
- 绝对定位通过锚点定位实现扁平化
- Grid通过二维布局实现扁平化
-
-
利用布局边界减少布局计算
- 对于组件的宽高不需要自适应的情况下,建议在UI描述时给定组件的宽高数值,当其组件外部的容器尺寸发生变化时,例如拖拽缩放等场景下,如果组件本身的宽高是固定的,理论上来讲,该组件在布局阶段不会参与Measure阶段,其节点中保存了对应的大小信息,如果组件内容较多时,由于避免了其中组件整体的测算过程,性能会带来较大的提升。
- 首次绘制的情况下,无论是否设置宽高属性,都会对所有组件进行布局和测算的过程,来得到最终的组件大小和位置。而当触发按钮修改外层Column的宽度时,也就是触发重新绘制的情况下,给定容器宽高为固定值的性能远远优于未设置宽高和设置百分比宽高,这是由于对于未设置宽高以及设置百分比宽高的情况下,在外层容器宽高发生变化时,组件本身也会触发重新进行Measure的过程,对组件的宽高进行重新测算,导致其布局时间很长,而设置了固定宽高的组件,则不会经过这一过程,而是直接使用初次绘制时保留的节点大小数据,减少了测算的时间,这对于性能的提升是尤为明显的,尤其是当组件内的内容十分复杂的情况下。
2.合理使用渲染控制语法
1. 合理控制元素显示与隐藏
控制元素显示与隐藏是一种常见的场景,使用Visibility.None、if条件判断等都能够实现该效果。其中if条件判断控制的是组件的创建、布局阶段,visibility属性控制的是元素在布局阶段是否参与布局渲染。使用时如果使用的方式不当,将引起性能上的问题。
- 只有初始的一次渲染或者交互次数很少的情况下,建议使用if条件判断来控制元素的显示与隐藏效果,对于内存有较大提升;
- 如果会频繁响应显示与隐藏的交互效果,建议使用切换Visibility.None和Visibility.Visible来控制元素显示与隐藏,提高性能;
- 通过if条件判断控制显示与隐藏的方式:
- 对比判断值为true和false时,初次加载过程中Measure、Layout时间明显存在区别,并且从组件创建时间可以判断,加载时会根据初始值判断是否创建对应组件内容。
- 当条件为false时,对应的组件内容不参与创建、Measure和Layout阶段。
- 通过visibility控制显示与隐藏的方式:
- 在初次加载时,无论visibility的值为Visibility.None还是Visibility.Visible都会创建对应组件内容。
- visibility属性控制的是Measure和Layout阶段,当visibility属性为Visibility.None时,对应的组件不参与Layout。
2.合理使用懒加载与组件复用
-
懒加载:
- 在100条数据范围内ForEach和LazyForEach差距不大,总体而言两者各项性能指标都在可接受范围内,而ForEach代码逻辑比LazyForEach简单,此场景下使用ForEach即可。
- 当数据大于1000条,特别是当数据达到10000条时,ForEach在列表渲染、应用内存占用、丢帧率等各个方面都会有“指数级别”的显著劣化,滑动会出现明显的卡顿,甚至会出现应用crash等现象。
- 使用LazyForEach除了最后内存稍微增大以外,其列表渲染时间、丢帧率都不会出现明显变化,具有较好的性能。
- 搭载懒加载属性:cacheCount,不适用时默认值为1;懒加载场景只会预加载List显示区域外cachedCount的内容
[!CAUTION]
-
List设置cachedCount后,显示区域外上下各会预加载并布局cachedCount行ListItem。
-
计算ListItem行数时,会计算ListItemGroup内部的ListItem行数。如果ListItemGroup内没有ListItem,则整个ListItemGroup算一行。
-
List下嵌套使用LazyForEach,并且LazyForEach下嵌套使用ListItemGroup时,LazyForEach会在List显示区域外上下各会创建cachedCount个ListItemGroup。
-
组件复用:
对于使用LazyForEach的情况下,在滑动过程中由于要动态创建组件,会出现BuildLazyItem的耗时,通过组件复用能力,可以减少滑动过程中的组件创建耗时,进一步优化滑动时的性能。
3.合理使用布局组件
1.选择合适的布局组件
常用的基础布局组件包含以下组件:
- Row、Column构建线性布局;
- Stack构建层叠布局;
复杂布局能力:
Flex构建弹性布局;
List既具备线性布局的特点,同时支持懒加载和滑动的能力;
Grid/GridItem提供了宫格布局的能力,同时也支持懒加载和滑动能力;
RelativeContainer是一种相对布局,通过描述各个内容组件间相互关系来指导内容元素的布局过程,可从横纵两个方面进行布局描述,是一种二维布局算法
- 在布局深度和节点数相同的情况下:
- 使用基础组件如Column和Row容器的性能明显高于其他布局;
- Flex的性能明显低于Column和Row容器,这是由于Flex本身带来的二次布局的影响;
- Grid/GridItem布局、相对布局RelativeContainer的性能消耗高于基础组件Column、Row、Stack等容器;
- 使用布局时尽量遵循以下原则:
- 在相同嵌套层级的情况下,如果多种布局方式可以实现相同布局效果,优选低耗时的布局,如使用Column、Row替代Flex实现相同的单行布局;
- 在能够通过其他布局大幅优化节点数的情况下,可以使用高级组件替代,如使用RelativeContainer替代Row、Column实现扁平化布局,此时其收益大于布局组件本身的性能差距;
- 仅在必要的场景下使用高耗时的布局组件,如使用Flex实现折行布局、使用Grid实现二维网格布局等;
2.Scroll嵌套List场景下,给定List组件宽高
在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。
Scroll嵌套List时:
- List没有设置宽高时,List的所有子组件ListItem都会参与布局。
- List设置宽高,只有布局区域内的ListItem子组件会参与布局。
- List使用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
Scroll嵌套List时:
- List没有设置宽高时,List的所有子组件ListItem都会参与布局。
- List设置宽高,只有布局区域内的ListItem子组件会参与布局。
- List使用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
- List使用LazyForEach加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。