HarmonyOS NEXT(开发进阶)合理利用结构实践

打印 上一主题 下一主题

主题 1839|帖子 1839|积分 5517

鸿蒙NEXT开发实战往期必看文章:
一分钟了解”纯血版!鸿蒙HarmonyOS Next应用开发
“非常详细的” 鸿蒙HarmonyOS Next应用开发学习门路!(从零底子入门到精通)
HarmonyOS NEXT应用开发案例实践总结合(持续更新......)
HarmonyOS NEXT应用开发性能优化实践总结(持续更新......)

ArkUI框架执行流程

在利用ArkUI开发中,我们通过结构组件和底子组件进行界面描述,这些描述会呈现出一个组件树的结构,底子组件在其中为叶子结点,结构组件则是中心节点,可以把这棵树称之为应用组件树。当用户执行交互(滑动,点击等活动)时会触发界面修改,界面的修改本质上是通过触发这棵组件树的重新渲染,来实现应用界面更新的过程。


应用界面更新的过程主要分为两个过程:数据处置惩罚过程和UI更新过程
1、数据处置惩罚过程中主要是对状态数据进行更新,状态数据指得是所界说的@State等相关的数据。数据变化时,会有肯定的更新耗时,并且数据关联的组件数量,也影响下一步UI更新的耗时,那么对于开发过程必要关注的,就是避免无效的数据更新导致冗余的UI更新操作。关于这部门的优化步伐可以参考《状态管理最佳实践》。
2、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的更新,可以利用结构界限淘汰子树更新的数量以及淘汰结构的计算。
精简节点数

结构阶段是接纳递归遍历所有节点的方式进行组件位置和大小的计算, 如果嵌套层级过深,将带来了更多的中心节点,在结构测算阶段下,额外的节点数将导致更多的计算过程,造成性能劣化。我们通过模拟了10、100、500、1000层Row嵌套的情况下,通过Profiler工具抓取Launch数据查看对应的首帧绘制,以及页面Measure/Layout时间进行对比。
  1.   Row() {
  2.     ... // 10、100、500、1000层Row容器嵌套
  3.     Row() {
  4.       Text('Inner Text')
  5.     }
  6.     ...
  7.   }
复制代码
然后进一步对比了在平铺的情况下,Row内组件个数在10、100、500、1000的条件下,利用Profiler工具抓取Launch的数据情况,得到如下效果如表1所示。
  1. Row() {
  2.     Row() {}
  3.     ... // 10、100、500、1000层Row容器并排
  4.     Text('Inner Text')
  5.   }
复制代码
表1    嵌套与平铺下的结构时间对比   对比指标
  10
  100
  500
  1000
  
嵌套/层
  首帧绘制
  3.2ms
  5.8ms
  17.3ms
  32ms
  Measure
  1.88ms
  2.89ms
  5.93ms
  10.46ms
  Layout
  0.38ms
  1.12ms
  5.26ms
  10.88ms
  
平铺 /个
  首帧绘制
  3.6ms
  4.5ms
  14ms
  24.3ms
  Measure
  2.15ms
  2.31ms
  5.61ms
  9.26ms
  Layout
  0.39ms
  1.38ms
  4.74ms
  9.92ms
  说明
以上数据来源均为版本DevEco Studio 4.0.3.415、SDK 4.0.10.9条件下测试得到,不同设备类型数据可能存在差异,测试数据旨在体现性能优化趋势,仅供参考。


根据以上数据对比发现,组件平铺和嵌套在雷同组件个数的情况下,其性能差异不大,并且整体上趋势保持同等,随着组件数量增加呈现线性增长的劣化,由此可以得到结论,真正影响结构性能的因素是参与结构的节点数量。所以在进行结构时,应该只管淘汰整体的节点数,来淘汰结构的性能劣化。
针对淘汰总节点,主要有两个方向:


  • 移除冗余的节点。
  • 利用扁平化结构淘汰节点数。
移除冗余节点
对于常出现冗余的情况,例如可能会在Row容器包含一个同样也是Row容器的子级。这种嵌套实际是多余的,并且会给结构层次结构造成不必要的开销。
  1. Row() {
  2.   Row(){
  3.     Image()
  4.     Text()
  5.   }
  6.   Image()
  7. }
复制代码
由于其中Row容器父子结构方向雷同,所以可以去掉Image和Text外层的Row来淘汰层级,如果视图更加复杂,结构在渲染时,会产生没有必要的计算。
  1. Row() {
  2.   Image()
  3.   Text()
  4.   Image()
  5. }
复制代码
尽管在这里只是多了一层,但是实际开发中的结构往往非常复杂,冗余带来的开销可能非常影响结构性能,尤其是在列表中动态创建组件时,带来的性能影响是显著的。
利用扁平化结构淘汰节点数
在某些情况下,开发者所实现的结构在嵌套层级上是没有冗余的,但是嵌套层级仍然较深,可能无法通过调整现有的结构方案,使其不包含多余的结构,唯一的解决方案可能是,通过切换到完全不同的结构类型来实现层次结构的扁平化。
例如图1中元素结构示意图,传统利用线性结构的情况下,统共存在4层嵌套、共15个节点,并且其中并没有冗余的嵌套节点。而扁平化结构是一种让页面结构变浅变宽的方式,通过一些高级组件如RelativeContainer、Grid等容器,可以让元素在平面上展开。这种结构方式可以或许有效淘汰由于利用线性结构带来的嵌套深度,将其用于描述结构的容器节点进行优化,到达精简节点数的目标。图一中将线性结构改成相对结构的情况下,嵌套2层、统共10个节点,相比之下前后少了5个节点。
图1 扁平化结构示意图

这种方式对于结构的影响主要体如今:

  • 页面创建时,扁平化淘汰了中心的嵌套层级,使总的组件节点的数量越少,在进行结构时所必要进行的计算相对越少。
  • 页面更新时,当要更新的结构是嵌套子树的结构,其树内包含过多节点时,整体更新会导致更新的节点数过多,造成结构性能劣化。
所以当页面不存在冗余节点时,可以考虑是否可以或许通过替换为更高级的结构使得页面扁平化,来到达淘汰节点数的目标。主要方式可以参考:


  • RelativeContainer 通过相对结构实现扁平化。
  • 绝对定位 通过锚点定位实现扁平化。
  • Grid 通过二维结构实现扁平化。
利用结构界限淘汰结构计算

对于组件的宽高不必要自适应的情况下,建议在UI描述时给定组件的宽高数值,当其组件外部的容器尺寸发生变化时,例如拖拽缩放等场景下,如果组件本身的宽高是固定的,理论上来讲,该组件在结构阶段不会参与Measure阶段,其节点中保存了对应的大小信息,如果组件内容较多时,由于避免了其中组件整体的测算过程,性能会带来较大的提拔。
我们通过修改以下示例代码中Column的宽度,对比给Row设置固定宽度.width(300).height(400)、百分比.width('100%').height('70%')以及不设置宽高的情况下的页面绘制、Measure、Layout时间。
  1. Column() {
  2.   Button("修改宽度").onClick(() => {
  3.     this.testWidth = '90%'
  4.   }).height('20%')
  5.   Row() {
  6.     // 400条文本数据
  7.   }
  8. }.width(this.testWidth )
复制代码
对比的效果如下:
表2    设置不同宽高对结构时间影响   对比指标/ms
  限定容器的宽高为固定值
  未设置容器的宽高
  限定容器的宽高为百分比
  首帧绘制
  60.20ms
  59.99ms
  60.50ms
  Measure
  17.80ms
  17.76ms
  16.92ms
  Layout
  5.5ms
  4.91ms
  4.92ms
  重新绘制
  2.0ms
  38.45ms
  42.62ms
  重绘的Measure
  0.50ms
  18.87ms
  20.93ms
  重绘的Layout
  0.12ms
  1.41ms
  1.80ms
  说明


  • 以上数据来源均为版本DevEco Studio 4.0.3.415、SDK 4.0.10.9条件下测试得到,不同设备类型数据可能存在差异,测试数据旨在体现性能优化趋势,仅供参考。
由上数据可以发现:


  • 首次绘制时,三种情况数据相差不大。
  • 重新绘制时,限定容器宽高为固定值的情况下,性能提拔显着。
分析原因可以得到,这是由于首次绘制的情况下,无论是否设置宽高属性,都会对所有组件进行结构和测算的过程,来得到终极的组件大小和位置。而当触发按钮修改外层Column的宽度时,也就是触发重新绘制的情况下,给定容器宽高为固定值的性能远远优于未设置宽高和设置百分比宽高,这是由于对于未设置宽高以及设置百分比宽高的情况下,在外层容器宽高发生变化时,组件本身也会触发重新进行Measure的过程,对组件的宽高进行重新测算,导致其结构时间很长,而设置了固定宽高的组件,则不会经过这一过程,而是直接利用初次绘制时保留的节点大小数据,淘汰了测算的时间,这对于性能的提拔是尤为显着的,尤其是当组件内的内容十分复杂的情况下。
所以对于可以或许在初期给定宽高的组件,在进行UI描述时只管给定宽高数值,可以或许淘汰由于容器尺寸变化造成的重新测算过程的性能。
合理利用渲染控制语法

合理控制元素表现与隐藏

控制元素表现与隐藏是一种常见的场景,利用Visibility.None、if条件判定等都可以或许实现该效果。其中if条件判定控制的是组件的创建、结构阶段,visibility属性控制的是元素在结构阶段是否参与结构渲染。利用时如果利用的方式不当,将引起性能上的问题。
对于不同的场景下,必要选择合适的手段,根据性能或者内存要求选择不同的实现方式:


  • 只有初始的一次渲染或者交互次数很少的情况下,建议利用if条件判定来控制元素的表现与隐藏效果,对于内存有较大提拔。
  • 如果会频仍相应表现与隐藏的交互效果,建议利用切换Visibility.None和Visibility.Visible来控制元素表现与隐藏,提高性能。
通过对一个复杂的视图结构,例如以下示例代码中,对包含100个Image组件的Column容器进行表现与隐藏控制,分别接纳if条件判定和visibility属性的方式进行控制。
  1. Row() {
  2.     Text("Hello World")
  3.     if(this.visible) {
  4.       Column() {
  5.         ... // 100个Image组件
  6.       }
  7.     }
  8.   }
复制代码
通过visibility属性控制的示例代码如下:
  1. Row() {
  2.     Text("Hello World")
  3.     Column() {
  4.       ... // 100个Image组件
  5.     }.visibility(this.visible?Visibility.Visible:Visibility.None)
  6.   }
复制代码
在雷同的测试情况下,分别测试在初次加载页面,以及改变状态变量this.visible的值来修改表现隐藏的情况下,通过Profiler工具抓取的结构时Measure、Layout以及组件创建的时长。
在初次加载的情况下的测试效果如下:
表3    利用if/else和visibility属性控制显隐的结构时间对比   对比指标
  if判定条件为true
  if判定条件为false
  Visibility.Visible
  Visibility.None
  组件创建时间
  13.67ms
  3.83ms
  13.38ms
  13.26ms
  Measure
  2.83ms
  0.92ms
  2.58ms
  2.24ms
  Layout
  3.79ms
  0.30ms
  2.14ms
  0.39ms
  说明
以上数据来源均为版本DevEco Studio 4.0.3.415、SDK 4.0.10.9条件下测试得到,不同设备类型数据可能存在差异,测试数据旨在体现性能优化趋势,仅供参考。
通过以上数据可以发现:


  • 通过if条件判定控制表现与隐藏的方式:   

    • 对比判定值为true和false时,初次加载过程中Measure、Layout时间显着存在区别,并且从组件创建时间可以判定,加载时会根据初始值判定是否创建对应组件内容。
    • 当条件为false时,对应的组件内容不参与创建、Measure和Layout阶段。

  • 通过visibility控制表现与隐藏的方式:   

    • 在初次加载时,无论visibility的值为Visibility.None照旧Visibility.Visible都会创建对应组件内容。
    • visibility属性控制的是Measure和Layout阶段,当visibility属性为Visibility.None时,对应的组件不参与Layout。

在切换表现状态的情况下的效果如下:
表4    利用if条件判定和visibility控制显隐的Measure/Layout时间对比   对比指标
  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
  说明
以上数据来源均为版本DevEco Studio 4.0.3.415、SDK 4.0.10.9条件下测试得到,不同设备类型数据可能存在差异,测试数据旨在体现性能优化趋势,仅供参考。
在切换表现状态的情况下:


  • 利用if条件判定切换表现时,组件会因为条件改变而判定是否参与创建、结构过程,切换过程会出现较大的Measure的性能消耗,原因是创建了新的组件,重新进行了Measure和Layout的过程。


  • 利用visibility的情况下,无论是否隐藏,组件在初次已经创建完成,并一直都存在组件树上,不会出现组件重新创建的过程,并且在Measure和Layout阶段的性能消耗比利用if/else的方式性能小很多,原因是组件的计算在首帧时已经计算过,不必要重复计算。
综上所述,在控制组件表现与隐藏时,建议遵照以下原则来选择利用控制方式:


  • 在对性能要求较高,并且会频仍切换元素的表现与隐藏的情况下,应该避免利用if条件判定,而改为通过visibility的属性控制,这样在切换Visibility.None和Visibility.Visible时,可以省去组件创建的时间,直接进入渲染过程。
  • 如果组件的创建非常消耗资源,且不会立即利用,也并非频仍切换交互的情况下,只在特定条件下才会出现时,可以通过if/else来进行内容的表现与隐藏控制,来到达懒加载的效果。
长列表利用懒加载与组件复用

在列表场景下会接纳List、Grid、WaterFlow等组件配合ForEach或者LazyForEach来实现,ForEach适合内容长度确定,内容在两屏以内的列表。LazyForEach适合长度超过两屏的列表情况,并且当内容结构相对固定的情况下,配合组件复用的方式来淘汰滑动过程中的组件创建。在长列表加载性能优化中,先容了较为详细的实践案例,这里我们仅引用一些关键性能收益数据。
懒加载
针对长列表这一场景,在当地模拟了10、100、1000、10000条数据,分别利用ForEach、LazyForEach,来测试关闭和开启懒加载情况下的完全表现所用时间、列表挂载时间、独占内存,并分析了其滑动过程中的丢帧率。其中,列表挂载时间是指创建组件和组件挂载数据的总时长。终极,利用IDE的Profiler工具检测下述指标,得到的数据如下所示:
表5    ForEach在不同数据量下的指标对比   ForEach对比指标
  10条数据
  100条数据
  1000条数据
  10000条数据
  完全表现所用时间
  1s 741ms
  1s 786ms
  1s 942ms
  5s 841ms
  列表挂载时间
  87ms
  88ms
  135ms
  3s 291ms
  独占内存(滑动完成后)
  38.2MB
  48.7MB
  83.7MB
  560.1MB
  丢帧率
  0.0%
  3.8%
  4.5%
  58.2%
  表6    LazyForEach在不同数据量下的指标对比   LazyForEach对比指标
  10条数据
  100条数据
  1000条数据
  10000条数据
  完全表现所用时间
  1s 544ms
  1s 486ms
  1s 652ms
  1s 707ms
  列表挂载时间
  88ms
  89ms
  94ms
  97ms
  独占内存(滑动完成后)
  38.1MB
  44.6MB
  46.3MB
  82.9MB
  丢帧率
  0.0%
  2.3%
  3.6%
  6.6%
  从测试数据可以看出:

  • 在100条数据范围内ForEach和LazyForEach差距不大,总体而言两者各项性能指标都在可担当范围内,而ForEach代码逻辑比LazyForEach简单,此场景下利用ForEach即可。
  • 当数据大于1000条,特别是当数据到达10000条时,ForEach在列表渲染、应用内存占用、丢帧率等各个方面都会有“指数级别”的显著劣化,滑动会出现显着的卡顿,甚至会出现应用crash等征象。
  • 利用LazyForEach除了最后内存稍微增大以外,其列表渲染时间、丢帧率都不会出现显着变化,具有较好的性能。

组件复用
对于利用LazyForEach的情况下,在滑动过程中由于要动态创建组件,会出现BuildLazyItem的耗时,通过组件复用本领,可以淘汰滑动过程中的组件创建耗时,进一步优化滑动时的性能。
对比长列表案例中开启组件复用和未开启的情况下,其数据如下:
表7 组件复用前后丢帧率和耗时分析   组件复用
  组件复用前
  组件复用后
  丢帧率
  3.7%
  0%
  BuildLazyItem耗时
  10.277ms
  0.749ms
  BuildRecycle耗时
  不涉及
  0.221ms
  总耗时
  13.430ms
  7.310ms
  可以发现列表滑动时丢帧率显着降低,这是因为,List列表开启了组件复用,不会执行BuildLazyItem这个耗时操作(耗时10.277ms),后续创建新组件节点时,会直接复用缓存区中的节点(耗时0.97ms),这样就大幅节约了组件重新创建的时间。
合理利用结构组件

选择合适的结构组件

在结构时,子组件会根据父组件的结构算法得到相应的排列规则,然后按照规则进行子组件位置的摆放。不同的结构容器利用的结构算法对性能带来的影响不同。开发者应该根据场景选用合适的结构,除非必须,只管淘汰利用性能差的结构组件。
我们常用的底子结构组件包含以下组件:


  • 利用Row、Column构建线性结构。
  • 利用Stack构建层叠结构。
上述结构都属于线性结构,顾名思义,在线性结构中排布的组件是按照特定的方向线性放置,如横向/纵向/Z序方向。除上述结构类型外,另有一些复杂结构本领,如Flex、List、Grid、RelativeContainer和自界说结构等。


  • 利用Flex构建弹性结构。


  • List既具备线性结构的特点,同时支持懒加载和滑动的本领。
  • Grid/GridItem提供了宫格结构的本领,同时也支持懒加载和滑动本领。
  • RelativeContainer是一种相对结构,通过描述各个内容组件间相互关系来指导内容元素的结构过程,可从横纵两个方面进行结构描述,是一种二维结构算法。
复杂结构提供了场景化的本领,解决一种或者多种结构场景。但是在一些场景下,不恰当的利用这些高级组件,可能带来更多的性能消耗。
我们通过对不同的结构方式,设置对应容器雷同的嵌套深度为5、总元素节点为20个Text的情况下,来对比其性能消耗。通过Profiler工具获取其首帧绘制时间进行对比。对比效果如下表:
表8    不同结构的首帧绘制时间对比   对比指标
  Column/Row
  Stack
  Flex
  RelativeContainer
  Grid/GridItem
  首帧绘制
  7.13ms
  7.34ms
  11.71ms
  9.13ms
  12.62ms
  Measure
  2.63ms
  2.70ms
  7.59ms
  3.59ms
  8.68ms
  Layout
  0.74ms
  0.77ms
  0.83ms
  0.77ms
  0.92ms
  说明
以上数据来源均为版本DevEco Studio 4.0.3.415、SDK 4.0.10.9条件下测试得到,不同设备类型数据可能存在差异,测试数据旨在体现性能优化趋势,仅供参考。
可以发现,在结构深度和节点数雷同的情况下:


  • 利用底子组件如Column和Row容器的性能显着高于其他结构。
  • Flex的性能显着低于Column和Row容器,这是由于Flex本身带来的二次结构的影响。
  • Grid/GridItem结构、相对结构RelativeContainer的性能消耗高于底子组件Column、Row、Stack等容器。
以上数据都是基于雷同结构层数和节点数的情况下的对比效果,反应了结构本身的相对性能消耗,并不意味着利用了该组件性能就肯定差,也并非任何情况下利用底子组件都可以或许保持良好的性能,因为在一些情况下,利用高级组件可以或许大大淘汰嵌套层数和节点数,其带来的性能提拔反而高于组件本身的性能消耗。所以在利用结构时只管遵照以下原则:


  • 在雷同嵌套层级的情况下,如果多种结构方式可以实现雷同结构效果,优选低耗时的结构,如利用Column、Row替换Flex实现雷同的单行结构。
  • 在可以或许通过其他结构大幅优化节点数的情况下,可以利用高级组件替换,如利用RelativeContainer替换Row、Column实现扁平化结构,此时其收益大于结构组件本身的性能差距。
  • 仅在必要的场景下利用高耗时的结构组件,如利用Flex实现折行结构、利用Grid实现二维网格结构等。
Scroll嵌套List场景下,给定List组件宽高

在利用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。
说明
Scroll嵌套List时:


  • List没有设置宽高时,List的所有子组件ListItem都会参与结构。
  • List设置宽高,只有结构区域内的ListItem子组件会参与结构。
  • List利用ForEach加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
  • List利用LazyForEach加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List表现区域内的子组件。
在如下代码案例中,通过Scroll嵌套List,对比List设置宽度和不设置的情况下:
  1. class BasicDataSource implements IDataSource {
  2.   private listeners: DataChangeListener[] = [];
  3.   private originDataArray: string[] = [];
  4.   public totalCount(): number {
  5.     return 0;
  6.   }
  7.   public getData(index: number): string {
  8.     return this.originDataArray[index];
  9.   }
  10.   registerDataChangeListener(listener: DataChangeListener): void {
  11.     if (this.listeners.indexOf(listener) < 0) {
  12.       console.info('add listener');
  13.       this.listeners.push(listener);
  14.     }
  15.   }
  16.   unregisterDataChangeListener(listener: DataChangeListener): void {
  17.     const pos = this.listeners.indexOf(listener);
  18.     if (pos >= 0) {
  19.       console.info('remove listener');
  20.       this.listeners.splice(pos, 1);
  21.     }
  22.   }
  23.   notifyDataReload(): void {
  24.     this.listeners.forEach(listener => {
  25.       listener.onDataReloaded();
  26.     })
  27.   }
  28.   notifyDataAdd(index: number): void {
  29.     this.listeners.forEach(listener => {
  30.       listener.onDataAdd(index);
  31.     })
  32.   }
  33.   notifyDataChange(index: number): void {
  34.     this.listeners.forEach(listener => {
  35.       listener.onDataChange(index);
  36.     })
  37.   }
  38.   notifyDataDelete(index: number): void {
  39.     this.listeners.forEach(listener => {
  40.       listener.onDataDelete(index);
  41.     })
  42.   }
  43.   notifyDataMove(from: number, to: number): void {
  44.     this.listeners.forEach(listener => {
  45.       listener.onDataMove(from, to);
  46.     })
  47.   }
  48. }
  49. export class MyDataSource extends BasicDataSource {
  50.   private dataArray: Array<string> = new Array(100).fill('test');
  51.   public totalCount(): number {
  52.     return this.dataArray.length;
  53.   }
  54.   public getData(index: number): string {
  55.     return this.dataArray[index];
  56.   }
  57.   public addData(index: number, data: string): void {
  58.     this.dataArray.splice(index, 0, data);
  59.     this.notifyDataAdd(index);
  60.   }
  61.   public pushData(data: string): void {
  62.     this.dataArray.push(data);
  63.     this.notifyDataAdd(this.dataArray.length - 1);
  64.   }
  65. }
复制代码
不设置宽高的代码样例如下:
  1. import { MyDataSource } from './MyDataSource';
  2. @Entry
  3. @Component
  4. struct NotSetHeightTestPage {
  5.   private data: MyDataSource = new MyDataSource();
  6.   build() {
  7.     Scroll() {
  8.       List() {
  9.         LazyForEach(this.data, (item: string, index: number ) => {
  10.           ListItem() {
  11.             Row() {
  12.               Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
  13.             }
  14.           }
  15.         })
  16.       }
  17.     }
  18.   }
  19. }
复制代码
设置固定高度的代码如下:
  1. import { MyDataSource } from './MyDataSource';;
  2. @Entry
  3. @Component
  4. struct SetHeightTestPage {
  5.   private data: MyDataSource = new MyDataSource();
  6.   build() {
  7.     Scroll() {
  8.       List() {
  9.         LazyForEach(this.data, (item: string, index: number ) => {
  10.           ListItem() {
  11.             Row() {
  12.               Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
  13.             }
  14.           }
  15.         })
  16.       }.width('100%').height(500)
  17.     }
  18.   }
  19. }
复制代码
通过IDE的Profiler抓取launch数据可以得到如下:
图2 List宽高不固定

图3 List宽高固定

表9 不设置List宽高与设置宽高对比数据   对比数据
  List宽高不固定
  List宽高固定
  结构使命数量LayoutTasks/个
  100
  12
  结构时间/ms
  32.43
  6.08
  未设置宽高的情况下,在进行结构时,从FlushLayoutTask以及FlushRenderTask的数据可以看到参与结构的组件数量是100个,设置了List宽高的情况下,从FlushLayoutTask以及FlushRenderTask的数据可以看到参与结构的组件数量是12个。说明对于Scroll嵌套List的情况下,如果不设置List宽高,由于Scroll是可滚动容器,其高度为无穷大,List在不设置的高度的情况下,高度也为无穷大,所以此时会创建所有的内容。而设置了高度的情况下,只会创建给定高度内的组件内容,所以为12个。体如今结构时间上,没有设置宽高的情况下,总体结构时间是32.43ms,设置了固定宽高数值的情况下,时间为6.08ms,大幅提拔了首次加载时的性能。


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表