一、ArkUI框架执行流程
在使用ArkUI开发中,我们通过布局组件和基础组件进行界面描述,这些描述会呈现出一个组件树的结构,基础组件在其中为叶子结点,布局组件则是中间节点,可以把这棵树称之为应用组件树。当用户执行交互(滑动,点击等行为)时会触发界面修改,界面的修改本质上是通过触发这棵组件树的重新渲染,来实现应用界面更新的过程。
应用界面更新的过程主要分为两个过程:数据处理过程和UI更新过程。
1、数据处理过程
数据处理过程中主要是对状态数据进行更新,状态数据指得是所定义的@State等相关的数据。数据变化时,会有一定的更新耗时,并且数据关联的组件数量,也影响下一步UI更新的耗时,那么对于开发过程需要关注的,就是避免无效的数据更新导致冗余的UI更新操作。
2、UI更新过程
UI更新过程中则是对需要更新的元素进行更新操作,对应的元素会经历Build、Measure、Layout和Render等阶段。其中Build是执行组件创建和组件标脏的过程,Measure是对组件的宽高进行测量的阶段,Layout是对元素进行在屏幕上位置进行摆放的阶段,而Render则是根据测量和布局得到的大小位置等信息,进行提交绘制的过程。
二、UI更新过程
UI更新过程包含组件标脏过程以及布局过程。在初次加载时,所有的组件都会经历这几个阶段(除去if/else条件为否的分支以及LazyForEach中未在可视区的内容),而在界面更新时(如列表滑动,切换显示隐藏状态,触发页面元素内容样式位置大小等变化等场景下),并不需要把页面所有的组件对象重新创建一遍,而是只需要对需要更新部分进行更新,而需要更新部分则是脏节点数组里包含的内容,UI线程处理过程会先将脏节点进行Build,Build的过程会按照组件id,依次更新组件设置的属性,如果属性发生改变,则进行组件标脏,其中布局属性(width、height、padding、margin等)发生改变会标记为布局脏,找到布局边界,进行子树更新,而非布局(Color、BackgroundColor、opacity等)属性仅会影响自身属性,不会进行子树查找。
多数情况下,需要我们关注的是重新布局带来的影响。如果某个组件的布局发生变化,一般也会对其他组件的布局也会产生影响,所以当有组件的布局发生变化,最简单的办法就是对整棵树进行重新布局,但是这样对整棵树进行重新布局的代价太大,所以需要降低重新布局带来的成本。实际上在一些特定场景下,组件发生变化后只需要对部分组件进行重新布局。标脏过程就是用来确定布局最小影响范围,来减少对整棵树进行重新布局的代价,而这个影响范围就是布局边界以内。
一般来讲,如果一个组件设置了固定的宽高尺寸,那这个组件就是布局边界。其内部组件布局的变化,不会影响到此布局边界外部的布局情况,那么在查找的时候,只需要在布局边界内部判断哪些组件的布局会受到影响,可以避免在整棵树结构的查找过程。
确定实际的脏节点数组后,根据脏节点数组来拿到对应的脏节点对象,通过递归遍历children进行Measure过程,如果该对象布局参数没有发生变化,就会跳过对应的Measure阶段。当Measure执行完成后,进行layout阶段。
从以上的过程可以看出,影响UI更新过程的主要因素是参与更新的节点数量。
在初次加载的时候,由于所有的节点都要参与全过程,那么如果对首帧渲染的速度有要求,就需要降低整体页面的组件节点数量。
在页面内容更新过程中,由于状态变量的变化导致UI的更新,可以利用布局边界减少子树更新的数量以及减少布局的计算。
三、合理使用布局方法
1、精简节点数
布局阶段是采用递归遍历所有节点的方式进行组件位置和大小的计算, 如果嵌套层级过深,将带来了更多的中间节点,在布局测算阶段下,额外的节点数将导致更多的计算过程,造成性能劣化。
针对减少总节点,主要有两个方向:1、移除冗余的节点 2、使用扁平化布局减少节点数。
扁平化布局示意图“”
这种方式对于布局的影响主要体现在:
- 页面创建时,扁平化减少了中间的嵌套层级,使总的组件节点的数量越少,在进行布局时所需要进行的计算相对越少。
- 页面更新时,当要更新的结构是嵌套子树的结构,其树内包含过多节点时,整体更新会导致更新的节点数过多,造成布局性能劣化。
2、利用布局边界减少布局计算
对于组件的宽高不需要自适应的情况下,建议在UI描述时给定组件的宽高数值,当其组件外部的容器尺寸发生变化时,例如拖拽缩放等场景下,如果组件本身的宽高是固定的,理论上来讲,该组件在布局阶段不会参与Measure阶段,其节点中保存了对应的大小信息,如果组件内容较多时,由于避免了其中组件整体的测算过程,性能会带来较大的提升。
3、合理使用渲染控制语法
(1)合理控制元素显示与隐藏
控制元素显示与隐藏是一种常见的场景,使用Visibility.None、if条件判断等都能够实现该效果。其中if条件判断控制的是组件的创建、布局阶段,visibility属性控制的是元素在布局阶段是否参与布局渲染。使用时如果使用的方式不当,将引起性能上的问题。
对于不同的场景下,需要选择合适的手段,根据性能或者内存要求选择不同的实现方式:
- 只有初始的一次渲染或者交互次数很少的情况下,建议使用if条件判断来控制元素的显示与隐藏效果,对于内存有较大提升。
- 如果会频繁响应显示与隐藏的交互效果,建议使用切换Visibility.None和Visibility.Visible来控制元素显示与隐藏,提高性能。
对比指标 | if判断条件为true | if判断条件为false | Visibility.Visible | Visibility.None |
---|---|---|---|---|
组件创建时间 | 13.67ms | 3.83ms | \ | \ |
Measure | 3.10ms | 0.13ms | 0.19ms | 0.10ms |
Layout | 1.64ms | 0.60ms | 0.27ms | 0.07ms |
综上所述,在控制组件显示与隐藏时,建议遵循以下原则来选择使用控制方式:
- 在对性能要求较高,并且会频繁切换元素的显示与隐藏的情况下,应该避免使用if条件判断,而改为通过visibility的属性控制,这样在切换Visibility.None和Visibility.Visible时,可以省去组件创建的时间,直接进入渲染过程。
- 如果组件的创建非常消耗资源,且不会立即使用,也并非频繁切换交互的情况下,只在特定条件下才会出现时,可以通过if/else来进行内容的显示与隐藏控制,来达到懒加载的效果。
(2)长列表使用懒加载与组件复用
3、选择合适的布局组件
在布局时,子组件会根据父组件的布局算法得到相应的排列规则,然后按照规则进行子组件位置的摆放。不同的布局容器使用的布局算法对性能带来的影响不同。开发者应该根据场景选用合适的布局,除非必须,尽量减少使用性能差的布局组件。