1# 性能提升的其他方法
2
3开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。
4
5## 设置List组件的宽高
6
7在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。
8
9>  **说明:**
10>
11>  Scroll嵌套List时:
12>
13>  - List没有设置宽高,会布局List的所有子组件。
14>
15>  - List设置宽高,会布局List显示区域内的子组件。
16>
17>  - List使用[ForEach](../quick-start/arkts-rendering-control-foreach.md)加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
18>
19>  - List使用[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md)加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。
20
21```ts
22class BasicDataSource implements IDataSource {
23  private listeners: DataChangeListener[] = [];
24  private originDataArray: string[] = [];
25
26  public totalCount(): number {
27    return 0;
28  }
29
30  public getData(index: number): string {
31    return this.originDataArray[index];
32  }
33
34  registerDataChangeListener(listener: DataChangeListener): void {
35    if (this.listeners.indexOf(listener) < 0) {
36      console.info('add listener');
37      this.listeners.push(listener);
38    }
39  }
40
41  unregisterDataChangeListener(listener: DataChangeListener): void {
42    const pos = this.listeners.indexOf(listener);
43    if (pos >= 0) {
44      console.info('remove listener');
45      this.listeners.splice(pos, 1);
46    }
47  }
48
49  notifyDataReload(): void {
50    this.listeners.forEach(listener => {
51      listener.onDataReloaded();
52    })
53  }
54
55  notifyDataAdd(index: number): void {
56    this.listeners.forEach(listener => {
57      listener.onDataAdd(index);
58    })
59  }
60
61  notifyDataChange(index: number): void {
62    this.listeners.forEach(listener => {
63      listener.onDataChange(index);
64    })
65  }
66
67  notifyDataDelete(index: number): void {
68    this.listeners.forEach(listener => {
69      listener.onDataDelete(index);
70    })
71  }
72
73  notifyDataMove(from: number, to: number): void {
74    this.listeners.forEach(listener => {
75      listener.onDataMove(from, to);
76    })
77  }
78}
79
80class MyDataSource extends BasicDataSource {
81  private dataArray: Array<string> = new Array(100).fill('test');
82
83  public totalCount(): number {
84    return this.dataArray.length;
85  }
86
87  public getData(index: number): string {
88    return this.dataArray[index];
89  }
90
91  public addData(index: number, data: string): void {
92    this.dataArray.splice(index, 0, data);
93    this.notifyDataAdd(index);
94  }
95
96  public pushData(data: string): void {
97    this.dataArray.push(data);
98    this.notifyDataAdd(this.dataArray.length - 1);
99  }
100}
101
102@Entry
103@Component
104struct MyComponent {
105  private data: MyDataSource = new MyDataSource();
106
107  build() {
108    Scroll() {
109      List() {
110        LazyForEach(this.data, (item: string, index: number ) => {
111          ListItem() {
112            Row() {
113              Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
114            }
115          }
116        })
117      }
118    }
119  }
120}
121```
122
123因此,此场景下建议设置List子组件的宽高。
124
125```ts
126class BasicDataSource implements IDataSource {
127  private listeners: DataChangeListener[] = [];
128  private originDataArray: string[] = [];
129
130  public totalCount(): number {
131    return 0;
132  }
133
134  public getData(index: number): string {
135    return this.originDataArray[index];
136  }
137
138  registerDataChangeListener(listener: DataChangeListener): void {
139    if (this.listeners.indexOf(listener) < 0) {
140      console.info('add listener');
141      this.listeners.push(listener);
142    }
143  }
144
145  unregisterDataChangeListener(listener: DataChangeListener): void {
146    const pos = this.listeners.indexOf(listener);
147    if (pos >= 0) {
148      console.info('remove listener')
149      this.listeners.splice(pos, 1);
150    }
151  }
152
153  notifyDataReload(): void {
154    this.listeners.forEach(listener => {
155      listener.onDataReloaded();
156    })
157  }
158
159  notifyDataAdd(index: number): void {
160    this.listeners.forEach(listener => {
161      listener.onDataAdd(index);
162    })
163  }
164
165  notifyDataChange(index: number): void {
166    this.listeners.forEach(listener => {
167      listener.onDataChange(index);
168    })
169  }
170
171  notifyDataDelete(index: number): void {
172    this.listeners.forEach(listener => {
173      listener.onDataDelete(index);
174    })
175  }
176
177  notifyDataMove(from: number, to: number): void {
178    this.listeners.forEach(listener => {
179      listener.onDataMove(from, to);
180    })
181  }
182}
183
184class MyDataSource extends BasicDataSource {
185  private dataArray: Array<string> = new Array(100).fill('test')
186
187  public totalCount(): number {
188    return this.dataArray.length;
189  }
190
191  public getData(index: number): string {
192    return this.dataArray[index];
193  }
194
195  public addData(index: number, data: string): void {
196    this.dataArray.splice(index, 0, data);
197    this.notifyDataAdd(index);
198  }
199
200  public pushData(data: string): void {
201    this.dataArray.push(data);
202    this.notifyDataAdd(this.dataArray.length - 1);
203  }
204}
205
206@Entry
207@Component
208struct MyComponent {
209  private data: MyDataSource = new MyDataSource();
210
211  build() {
212    Scroll() {
213      List() {
214        LazyForEach(this.data, (item: string, index: number) => {
215          ListItem() {
216            Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
217          }.width('100%')
218        })
219      }.width('100%').height(500)
220    }.backgroundColor(Color.Pink)
221  }
222}
223```
224
225![list1](figures/list1.gif)
226
227使用SmartPerf Host工具分别抓取List不设置宽高时和设置宽高时的trace数据。
228
229**List不设置宽高:**
230
231![list-trace-01](figures/arkts-performance-improvement-recommendation-list-trace-01.PNG)
232
233**List设置宽高:**
234
235![list-trace-02](figures/arkts-performance-improvement-recommendation-list-trace-02.PNG)
236
237从trace图可以看出,List不设置宽高时100个子组件全部参与布局,布局时间46.62ms。而给List设置宽高后只有给定高度内的12个子组件参与布局,布局时间减少到8.51ms,大幅提升了首次加载时的性能。
238
239## 使用Column/Row替代Flex
240
241由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。
242
243```ts
244@Entry
245@Component
246struct FlexBuild {
247  private data: string[] = new Array(20).fill('');
248  build() {
249    Flex({ direction: FlexDirection.Column }) {
250      Flex({ direction: FlexDirection.Column }) {
251        Flex({ direction: FlexDirection.Column }) {
252          Flex({ direction: FlexDirection.Column }) {
253            Flex({ direction: FlexDirection.Column }) {
254              ForEach(this.data, (item: string, index: number) => {
255                Text(`Item ${index}`)
256                  .width('100%')
257                  .textAlign(TextAlign.Center)
258              })
259            }
260          }
261        }
262      }
263    }
264  }
265}
266```
267
268上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。
269
270```ts
271@Entry
272@Component
273struct ColumnAndRowBuild {
274  private data: string[] = new Array(20).fill('');
275  build() {
276    Row() {
277      Row() {
278        Row() {
279          Row() {
280            Column() {
281              ForEach(this.data, (item: string, index: number) => {
282                Text(`Item ${index}`)
283                  .width('100%')
284                  .textAlign(TextAlign.Center)
285              })
286            }
287          }
288        }
289      }
290    }
291  }
292}
293```
294
295![flex1](figures/flex1.PNG)
296
297使用SmartPerf Host抓取上述两种不同布局方式示例程序的trace数据,对比其性能消耗,如下表所示。
298
299|对比指标|Flex布局|Column/Row|
300|--------|--------|--------|
301|Build耗时(ms)|4.27|2.51|
302|Measure耗时(ms)|2.98|1.04|
303|Layout耗时(ms)|0.34|0.24|
304
305可以看出布局深度和节点数相同的情况下,Flex的性能明显低于Column和Row容器,此时使用Column/Row替换Flex可以显著减少应用布局的性能消耗。
306
307## 减少应用滑动白块
308
309应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。
310如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。
311如下是使用cachedCount参数的例子:
312
313```ts
314@Entry
315@Component
316struct MyComponent {
317  private source: MyDataSource = new MyDataSource();
318
319  build() {
320    List() {
321      LazyForEach(this.source, (item:string) => {
322        ListItem() {
323          Text("Hello" + item)
324            .fontSize(50)
325            .onAppear(() => {
326              console.log("appear:" + item)
327            })
328        }
329      })
330    }.cachedCount(3) // 扩大数值appear日志范围会变大
331  }
332}
333
334class MyDataSource implements IDataSource {
335  data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
336
337  public totalCount(): number {
338    return this.data.length
339  }
340
341  public getData(index: number): number {
342    return this.data[index]
343  }
344
345  registerDataChangeListener(listener: DataChangeListener): void {
346  }
347
348  unregisterDataChangeListener(listener: DataChangeListener): void {
349  }
350}
351```
352![list2](figures/list2.gif)
353
354**使用说明:**
355cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。
356
357更多关于cachedCount的使用指导,请参考文档[列表场景性能提升实践](list-perf-improvment.md#缓存列表项)。