# 18.1:性能优化

本节笔者根据 OpenHarmony 官网的指导文档简单总结了以下几种方式,可有效提升应用性能,避免应用实现上带来的性能劣化。

# 18.1.1:列表性能优化

在使用 List 或者 Grid 等滚动组件时,一般是借助 ForEach 或者 LazyForEach 实现,它们的主要区别是: ForEach 会一次性加载所有的列表元素,这种加载方式一方面会导致页面的启动时间过长影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负担,而 LazyForEach 则是按需加载,它可以显著提升页面加载速度。

  • 推荐使用数据懒加载

    @Entry @Component struct Index {
    
      @State dataSet: number[] = Array.from(Array(100), (v, k) => k);
    
      build() {
        List() {
          ForEach(this.dataSet, (item: number) => {
            ListItem() {
              Text(`item value: ${item}`)
            }
          }, (item: number) => item.toString())
        }
        .width("100%")
        .height("100%")
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    上述代码会在页面加载时将 100 个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示:

    class BasicDataSource implements IDataSource {
    
      private listeners: DataChangeListener[] = []
    
      public totalCount(): number {
        return 0
      }
    
      public getData(index: number): any {
        return undefined
      }
    
      registerDataChangeListener(listener: DataChangeListener): void {
        if (this.listeners.indexOf(listener) < 0) {
          console.info('add listener')
          this.listeners.push(listener)
        }
      }
    
      unregisterDataChangeListener(listener: DataChangeListener): void {
        const pos = this.listeners.indexOf(listener);
        if (pos >= 0) {
          console.info('remove listener')
          this.listeners.splice(pos, 1)
        }
      }
    
      notifyDataReload(): void {
        this.listeners.forEach(listener => {
          listener.onDataReloaded()
        })
      }
    
      notifyDataAdd(index: number): void {
        this.listeners.forEach(listener => {
          listener.onDataAdd(index)
        })
      }
    
      notifyDataChange(index: number): void {
        this.listeners.forEach(listener => {
          listener.onDataChange(index)
        })
      }
    
      notifyDataDelete(index: number): void {
        this.listeners.forEach(listener => {
          listener.onDataDelete(index)
        })
      }
    
      notifyDataMove(from: number, to: number): void {
        this.listeners.forEach(listener => {
          listener.onDataMove(from, to)
        })
      }
    }
    
    class MyDataSource extends BasicDataSource {
    
      private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2']
    
      public totalCount(): number {
        return this.dataArray.length
      }
    
      public getData(index: number): any {
        return this.dataArray[index]
      }
    
      public addData(index: number, data: string): void {
        this.dataArray.splice(index, 0, data)
        this.notifyDataAdd(index)
      }
    
      public pushData(data: string): void {
        this.dataArray.push(data)
        this.notifyDataAdd(this.dataArray.length - 1)
      }
    }
    
    @Entry @Component struct MyComponent {
    
      private data: MyDataSource = new MyDataSource()
    
      build() {
        List() {
          LazyForEach(this.data, (item: string) => {
            ListItem() {
              Row() {
                Text(item).fontSize(20).margin({ left: 10 })
              }
            }
            .onClick(() => {
              this.data.pushData('item value: ' + this.data.totalCount())
            })
          }, item => item)
        }
        .width("100%")
        .height("100%")
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102

    上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。

  • 合理设置应用滑动白块

    ListGrid 组件提供了 cachedCount 属性,该属性表示 List 或者 Grid 组件在视图外预加载 item 的个数,应用可以通过设置改参数来调整 UI 的加载范围,比如需要请求网络图片,可以在 item 滑动到屏幕显示之前提前加载好内容从而减少滑动白块。以下是使用 cachedCount 参数的例子:

    @Entry @Component struct Index {
    
      private source: MyDataSource = new MyDataSource();
    
      build() {
        List() {
          LazyForEach (this.source, item => {
            ListItem() {
              Text("Hello" + item)
                .fontSize(100)
                .onAppear(()=>{
                  console.log("appear:" + item)
                })
            }
          })
        }.cachedCount(3)  // 扩大数值appear日志范围会变大
      }
    }
    
    class MyDataSource implements IDataSource {
      
      private data: number[] = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
      
      public totalCount(): number {
        return this.data.length
      }
      
      public getData(index: number): any {
        return this.data[index]
      }
      
      registerDataChangeListener(listener: DataChangeListener): void {
      }
      
      unregisterDataChangeListener(listener: DataChangeListener): void {
      }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38

    📢:cachedCount 的增加会增大 UI 的 cpu 和内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。

  • 设置 List 组件的宽高

    开发者在使用 Scroll 组件嵌套 List 组件时,若不指定 List 组件的宽高,则 List 默认加载全部数据,示例如下:

    @Entry @Component struct Index {
    
      private dataSet: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    
      build() {
        Scroll() {
          List() {
            ForEach(this.dataSet, (item) => {
              ListItem() {
                Text(`item value: ${item}`).fontSize(30).margin({ left: 10 })
              }.height(100)
            }, (item) => item.toString())
          }
        }.backgroundColor(Color.Pink)
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    当示例启动后,List 组件会加载所有的组件元素,在这种场景下建议开发者设置 List 组件的宽高,如下所示:

    @Entry @Component struct Index {
    
      private dataSet: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    
      build() {
        Scroll() {
          List() {
            ForEach(this.dataSet, (item) => {
              ListItem() {
                Text(`item value: ${item}`).fontSize(30).margin({ left: 10 })
              }
              .height(100)
            }, (item) => item.toString())
          }
          .width('100%') // 设置宽
          .height(500)   // 设置高
        }.backgroundColor(Color.Pink)
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

# 18.1.2:使用 Column/Row 替代 Flex

由于 Flex 容器组件默认情况下存在 shrink 导致二次布局,这会在一定程度上造成页面渲染上的性能劣化,示例如下:

@Entry @Component struct Index {
  build() {
    Flex({ direction: FlexDirection.Column }) {
      Flex()
        .width(300)
        .height(200)
        .backgroundColor(Color.Pink)
      Flex()
        .width(300)
        .height(200)
        .backgroundColor(Color.Yellow)
      Flex()
        .width(300)
        .height(200)
        .backgroundColor(Color.Grey)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

上述代码可将 Flex 替换为 ColumnRow,在保证实现的页面布局效果相同的前提下避免 Flex 二次布局带来的负面影响。

@Entry @Component struct Index {
  build() {
    Column() {
      Row()
        .width(300)
        .height(200)
        .backgroundColor(Color.Pink)
      Row()
        .width(300)
        .height(200)
        .backgroundColor(Color.Yellow)
      Row()
        .width(300)
        .height(200)
        .backgroundColor(Color.Grey)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 18.1.3:使用条件渲染替代显隐控制

如下所示,开发者在使用 visibility 通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。

@Entry @Component struct Index {

  @State isVisible: Visibility = Visibility.Visible;

  build() {
    Column() {
      Button("显隐切换")
        .onClick(() => {
          if (this.isVisible == Visibility.Visible) {
            this.isVisible = Visibility.None
          } else {
            this.isVisible = Visibility.Visible
          }
        })
      Row()
        .visibility(this.isVisible) // 显隐控制
        .width(300)
        .height(300)
        .backgroundColor(Color.Pink)
    }.width('100%')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

要避免这一问题,可使用 if 条件渲染代替 visibility 属性变换,如下所示:

@Entry @Component struct MyComponent {

  @State isVisible: boolean = true;

  build() {
    Column() {
      Button("显隐切换")
        .onClick(() => {
          this.isVisible = !this.isVisible
        })
      if(this.isVisible) { // 使用 if 语句
        Row()
          .width(300)
          .height(300)
          .backgroundColor(Color.Pink)
      }
    }.width('100%')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 18.1.4:小结

以上就是笔者根据 OpenHarmony 官网文档归纳总结而来的开发技巧供读者参考,也非常欢迎读者给本网站提供更多的开发样例。

(adsbygoogle = window.adsbygoogle || []).push({});
请作者喝杯咖啡

津公网安备 12011402001367号

津ICP备2020008934号-2