HarmonyOS:ArkUI性能优化实践(1)布局性能优化
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤博客园地址:为敢技术(https://www.cnblogs.com/strengthen/ )
➤GitHub地址:https://github.com/strengthen
➤原文地址:https://www.cnblogs.com/strengthen/p/18491885
➤如果链接不是为敢技术的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
在ArkUI的布局过程中,应用侧会根据前端UI描述创建后端的页面节点树,其中包含了处理UI事件属性更新、布局测算、事件处理等逻辑。例如:代码示例中的Index,InfoView组件会生成CustomNode节点,CustomNode是自定义组件节点,用于处理自定义组件相关业务逻辑,例如执行build函数,Row、Text组件等会生成对应的FrameNode节点,FrameNode是系统组件节点,在这个过程中,UI线程会对每个元素进行测算和布局,来确定具体的位置和大小,其中测量Measure阶段负责确定组件对象的测量宽高,也就是该组件元素需要占用屏幕的大小,然后会在Layout阶段确定组件的最终宽/高和四个顶点的位置,确定了具体元素的节点位置信息后,会根据页面节点树生成当前的界面描述数据结构——渲染树。渲染树由RenderNode渲染节点组成,描述了具体的元素在屏幕上的布局信息,包含大小、位置以及一些其他属性。最后渲染服务的渲染线程会根据渲染树的信息执行相应的绘制工作。在布局阶段中,若视图嵌套层级深,节点数过多,会导致在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布局来实现扁平化,减少节点数,从而提升布局性能。
在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和Layout的耗时明显降低,这时由于对未设置宽高以及设置百分比宽高的情况下,外层容器宽高发生变化时,组件本身也会触发重新进行Measure的过程,对组件的宽高进行重新测算,导致其布局时间很长,而设置了固定宽高的组件,则不会经过这一过程,而是直接使用初次绘制时保留的节点大小数据。减少了Measure的时间,这对于性能的提升是尤为明显的,尤其是当组件的内容十分复杂的情况下,所以对于能够在初期给定宽高的组件,在进行UI描述时,尽量给定宽高的大小,能够减少由于容器尺寸变化造成的重新测算过程的性能。
以上数据只是基于相同布局层数和节点数的情况下的对比结果,反应了布局本身的相对性能消耗,但不意味着使用该组件性能就一定差,也不是任何情况下使用基础组件都能够保持良好的性能。因为在一些情况下,使用高级组件能够大大减少嵌套节点层数和节点数,其带来的性能提升反而高于组件本身的性能消耗。所以在使用布局时,尽量遵循以下原则:
(1)、在相同嵌套层级的情况下,如果多种布局方式都可以实现相同布局效果,优先低耗时的布局,如使用Column、Row替代Flex实现相同的单行布局。