为有牺牲多壮志,敢教日月换新天。

HarmonyOS:ArkUI性能优化实践(1)布局性能优化

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤博客园地址:为敢技术(https://www.cnblogs.com/strengthen/ 
➤GitHub地址:https://github.com/strengthen
➤原文地址:https://www.cnblogs.com/strengthen/p/18491885
➤如果链接不是为敢技术的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

一、布局流程简介
在使用UI描述进行布局时,如果布局方式使用不当,比如布局嵌套层级过多,或者使用了不恰当的布局组件,会导致布局阶段的时间变长,导致用户在使用应用时遇到卡顿、掉帧、响应慢等现象,会影响用户体验,性能差的布局很有可能使用过多的内存资源,导致占用过多的内存,使设备性能下降,可能导致应用或系统崩溃。优化布局性能可以使界面的响应速度更快,减少延迟和卡顿,减少资源占用,从而提升用户体验,提高应用的可用性和吸引力。

在ArkUI的布局过程中,应用侧会根据前端UI描述创建后端的页面节点树,其中包含了处理UI事件属性更新、布局测算、事件处理等逻辑。例如:代码示例中的Index,InfoView组件会生成CustomNode节点,CustomNode是自定义组件节点,用于处理自定义组件相关业务逻辑,例如执行build函数,Row、Text组件等会生成对应的FrameNode节点,FrameNode是系统组件节点,在这个过程中,UI线程会对每个元素进行测算和布局,来确定具体的位置和大小,其中测量Measure阶段负责确定组件对象的测量宽高,也就是该组件元素需要占用屏幕的大小,然后会在Layout阶段确定组件的最终宽/高和四个顶点的位置,确定了具体元素的节点位置信息后,会根据页面节点树生成当前的界面描述数据结构——渲染树。渲染树由RenderNode渲染节点组成,描述了具体的元素在屏幕上的布局信息,包含大小、位置以及一些其他属性。最后渲染服务的渲染线程会根据渲染树的信息执行相应的绘制工作。在布局阶段中,若视图嵌套层级深,节点数过多,会导致在Measure和Layout的过程中,通过遍历测量组件大小和边界的时间过长,造成额外的计算。所以优化布局性能可以从减少节点数,或减少测算布局耗时方面来考虑,

二、精简节点数
几个常用的优化布局结构的方法:
1、通过精简节点数来优化布局性能:
通过数据对比来看一下节点数量对布局时间的影响,分别在10、100、500和1000层Row容器嵌套的情况下,通过IDE的Profiler工具抓取Launc的数据情况,查看对应的Measure和Layout时间进行对比,可以看出:随着组件数量增加,性能呈现线性增长的劣化趋势。

 2、进一步对比在嵌套和平铺的情况下,Row内组件个数在10、100、500和1000的条件下,通过IDE的Profiler工具抓取Launc的数据情况,其结果显示:组件平铺和嵌套在相同组件个数的情况下,性能差异不大,并且整体上趋势保持一致,由此可以得到结论:真正影响布局性能的因素是参与布局的节点数量。所以在进行布局时,应该尽量减少参与布局的节点数,来减少布局的性能消耗,针对减少总节点数,主要有两个优化方向:

(1)、移除冗余的节点。

(2)、使用扁平化布局减少节点数。

3、对于常出现冗余的情况,例如下图示例中Row容器包含一个同样也是Row容器的子容器,由于其中Row容器父子布局方向相同,所以可以去掉Image和Text外层的Row,来减少节点数。左侧代码的这种嵌套实际是多余的,并且给布局层次结构造成不必要的开销。

4、在一些情况下,实现的布局在嵌套层级上是没有冗余的,但是嵌套层级仍然较深,这时可以考虑通过切换到完全不同的布局类型来实现布局的扁平化。如下图中图1所示的布局,在使用线性布局的情况下,总共存在4层嵌套、15个节点,其中并没有冗余的嵌套节点,此时可以使用下图中图3中的方式,采用相对布局实现扁平化,让页面结构变浅变宽,将元素在平面上展开,相比之前线性布局的方式,嵌套层级由4层降低为2层。节点数由15个降低为10个,可以看出,通过这种布局优化方式,能够有效减少布局嵌套深度,达到精简节点数的目的,所以当页面不存在冗余节点时,但嵌套层级仍然很深的情况,可以考虑使用如:RelativeContainer、Grid布局来实现扁平化,减少节点数,从而提升布局性能。

三、合理控制元素显示与隐藏
1、在布局描述中,控制元素显示与隐藏时一种常见的场景,通过合理的方式控制元素显示与隐藏,可以有效提升布局性能。

在ArkUI中,可以通过if条件渲染或者设置不同的visibility属性值,来达到控制元素显示与隐藏的效果,当需要控制显示与隐藏的视图内容比较复杂时,选择合适的控制方式就显得尤为重要,例如下图示例代码中,对包含100个Image组件的Column容器进行显示与隐藏控制,采用if条件判断和visibility属性的方式进行控制。

2、在相同的测试环境下,分别测试在初次加载页面和改变状态变量,this.visible的值来修改显示隐藏的情况,通过Profiler工具抓取的布局时,Measure、Layout时长进行对比,if条件判断控制显示与隐藏的方式加载时,会根据条件值为true或false,判断是否创建对应的组件内容,当条件为false时,对应的组件内容不参与Measure和Layout阶段。

3、而对于visibility属性控制显示与隐藏的方式,无论visibility的值为Visibility.Visiable还是Visibility.None,都会创建对应组件内容,当visibility属性为Visibility.None时,对应的组件不参与Layout阶段。

4、在切换显示状态的情况下,使用if条件判断切换显示时,组件会因为条件改变而判断是否参与创建、布局过程,切换过程会出现较大的Measure的性能消耗,原因是创建了新的组件,重新进行了Measure和Layout的过程。

5、使用visibility的情况下,无论是否隐藏,组件都参与了创建,并一直都存在组件树上,不会出现组件重新创建的过程,并且在Measure和Layout阶段的性能消耗,比使用if条件渲染的方式性能小很多,原因是组件的计算,在初次加载时已经计算过,不需要重复计算,所以,在控制组件显示与隐藏时,建议遵循以下原则来使用控制方式:

(1)、在对性能要求高,并且会频繁切换元素的显示与隐藏的情况下,应该避免使用if条件判断,而改为通过visibility的属性控制。

(2)、如果组件的创建非常消耗资源,且不会立即使用,也并非频繁切换交互的情况下,只在特定条件下才会出现,可以通过if条件渲染来进行内容的显示与隐藏控制,达到懒加载效果。

四、给定组件的宽高
在拖拽、缩放时,如果部分内容的大小不需要自适应,可以通过给对应的组件设置固定宽高,来减少Measure过程的耗时。
通过如下代码中点击修改外层Column的宽度来模拟缩放场景。对比给内层Row设置宽高的三种情况:
(1)、设置宽高分别为固定值300和400
(2)、设置宽高分别为100%和70%
(3)、不设置宽高属性
对比在初次加载和点击修改宽度的情况下的Measure、Layout耗时,从下图可以看出:
在初次加载情况下,三种情况数据相差不大,这是由于在首次加载的情况下,所有组件都会参与布局的过程。

在触发重新绘制的情况下,可以看到,宽高为固定值相比较设置为百分比和不设置,Measure和Layout的耗时明显降低,这时由于对未设置宽高以及设置百分比宽高的情况下,外层容器宽高发生变化时,组件本身也会触发重新进行Measure的过程,对组件的宽高进行重新测算,导致其布局时间很长,而设置了固定宽高的组件,则不会经过这一过程,而是直接使用初次绘制时保留的节点大小数据。减少了Measure的时间,这对于性能的提升是尤为明显的,尤其是当组件的内容十分复杂的情况下,所以对于能够在初期给定宽高的组件,在进行UI描述时,尽量给定宽高的大小,能够减少由于容器尺寸变化造成的重新测算过程的性能。

五、使用推荐的布局组件
不同的布局容器使用的布局算法对性能带来的影响不同,应该根据场景选择合适的布局,尽量优先使用推荐的布局组件。高级布局提供了场景化的能力,解决一种或多种布局场景,但是在一些场景下不恰当的使用高级组件可能带来更多的性能消耗。通过对不同的布局方式,设置对应容器相同的嵌套深度为5,总节点数为20个Text的情况下,来对比其性能消耗,对比结果如下图所示,可以发现:
(1)、在布局深度和节点数相同的情况下,使用基础组件,如:Column和Row容器的性能,明显高于其他布局。
(2)、Flex的性能明显低于Colum和Row容器。
(3)、Grid、RelativeContainer的性能消耗高于Column、Row、Stack等容器。

以上数据只是基于相同布局层数和节点数的情况下的对比结果,反应了布局本身的相对性能消耗,但不意味着使用该组件性能就一定差,也不是任何情况下使用基础组件都能够保持良好的性能。因为在一些情况下,使用高级组件能够大大减少嵌套节点层数和节点数,其带来的性能提升反而高于组件本身的性能消耗。所以在使用布局时,尽量遵循以下原则:

(1)、在相同嵌套层级的情况下,如果多种布局方式都可以实现相同布局效果,优先低耗时的布局,如使用Column、Row替代Flex实现相同的单行布局。

(2)、在能够通过其他布局大幅优化节点数的情况下,可以使用高级组件替代,例如使用RelativeContainer替代Row、Column实现扁平化布局,此时其收益大于布局组件本身的性能差距。
(3)、仅在必要的场景下使用高耗时的布局组件,如使用Flex实现折行布局、使用Grid实现二维网格布局等。
posted @ 2024-10-22 09:43  为敢技术  阅读(2)  评论(0编辑  收藏  举报