• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 性能提升的其他方法
2
3开发者若使用低性能的代码实现功能场景可能不会影响应用的正常运行,但却会对应用的性能造成负面影响。本章节列举出了一些可提升性能的场景供开发者参考,以避免应用实现上带来的性能劣化。
4
5## 使用数据懒加载
6
7开发者在使用长列表时,如果直接采用循环渲染方式,如下所示,会一次性加载所有的列表元素,一方面会导致页面启动时间过长,影响用户体验,另一方面也会增加服务器的压力和流量,加重系统负担。
8
9```ts
10@Entry
11@Component
12struct MyComponent {
13  @State arr: number[] = Array.from(Array<number>(100), (v:number,k:number) =>k);  //构造0-99的数组
14  build() {
15    List() {
16      ForEach(this.arr, (item: number) => {
17        ListItem() {
18          Text(`item value: ${item}`)
19        }
20      }, (item: number) => item.toString())
21    }
22  }
23}
24```
25
26上述代码会在页面加载时将100个列表元素全部加载,这并非我们需要的,我们希望从数据源中按需迭代加载数据并创建相应组件,因此需要使用数据懒加载,如下所示:
27
28```ts
29class BasicDataSource implements IDataSource {
30  private listeners: DataChangeListener[] = [];
31  private originDataArray: string[] = [];
32
33  public totalCount(): number {
34    return 0;
35  }
36
37  public getData(index: number): string {
38    return this.originDataArray[index];
39  }
40
41  registerDataChangeListener(listener: DataChangeListener): void {
42    if (this.listeners.indexOf(listener) < 0) {
43      console.info('add listener');
44      this.listeners.push(listener);
45    }
46  }
47
48  unregisterDataChangeListener(listener: DataChangeListener): void {
49    const pos = this.listeners.indexOf(listener);
50    if (pos >= 0) {
51      console.info('remove listener');
52      this.listeners.splice(pos, 1);
53    }
54  }
55
56  notifyDataReload(): void {
57    this.listeners.forEach(listener => {
58      listener.onDataReloaded();
59    })
60  }
61
62  notifyDataAdd(index: number): void {
63    this.listeners.forEach(listener => {
64      listener.onDataAdd(index);
65    })
66  }
67
68  notifyDataChange(index: number): void {
69    this.listeners.forEach(listener => {
70      listener.onDataChange(index);
71    })
72  }
73
74  notifyDataDelete(index: number): void {
75    this.listeners.forEach(listener => {
76      listener.onDataDelete(index);
77    })
78  }
79
80  notifyDataMove(from: number, to: number): void {
81    this.listeners.forEach(listener => {
82      listener.onDataMove(from, to);
83    })
84  }
85}
86
87class MyDataSource extends BasicDataSource {
88  private dataArray: string[] = ['item value: 0', 'item value: 1', 'item value: 2'];
89
90  public totalCount(): number {
91    return this.dataArray.length;
92  }
93
94  public getData(index: number): string {
95    return this.dataArray[index];
96  }
97
98  public addData(index: number, data: string): void {
99    this.dataArray.splice(index, 0, data);
100    this.notifyDataAdd(index);
101  }
102
103  public pushData(data: string): void {
104    this.dataArray.push(data);
105    this.notifyDataAdd(this.dataArray.length - 1);
106  }
107}
108
109@Entry
110@Component
111struct MyComponent {
112  private data: MyDataSource = new MyDataSource();
113
114  build() {
115    List() {
116      LazyForEach(this.data, (item: string) => {
117        ListItem() {
118          Row() {
119            Text(item).fontSize(20).margin({ left: 10 })
120          }
121        }
122        .onClick(() => {
123          this.data.pushData('item value: ' + this.data.totalCount());
124        })
125      },(item:string):string => item)
126    }
127  }
128}
129```
130
131![LazyForEach1](figures/LazyForEach1.gif)
132
133上述代码在页面加载时仅初始化加载三个列表元素,之后每点击一次列表元素,将增加一个列表元素。
134
135## 设置List组件的宽高
136
137在使用Scroll容器组件嵌套List组件加载长列表时,若不指定List的宽高尺寸,则默认全部加载。
138
139>  **说明:**
140>
141>  Scroll嵌套List时:
142>
143>  - List没有设置宽高,会布局List的所有子组件。
144>
145>  - List设置宽高,会布局List显示区域内的子组件。
146>
147>  - List使用[ForEach](../quick-start/arkts-rendering-control-foreach.md)加载子组件时,无论是否设置List的宽高,都会加载所有子组件。
148>
149>  - List使用[LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md)加载子组件时,没有设置List的宽高,会加载所有子组件,设置了List的宽高,会加载List显示区域内的子组件。
150
151```ts
152class BasicDataSource implements IDataSource {
153  private listeners: DataChangeListener[] = [];
154  private originDataArray: string[] = [];
155
156  public totalCount(): number {
157    return 0;
158  }
159
160  public getData(index: number): string {
161    return this.originDataArray[index];
162  }
163
164  registerDataChangeListener(listener: DataChangeListener): void {
165    if (this.listeners.indexOf(listener) < 0) {
166      console.info('add listener');
167      this.listeners.push(listener);
168    }
169  }
170
171  unregisterDataChangeListener(listener: DataChangeListener): void {
172    const pos = this.listeners.indexOf(listener);
173    if (pos >= 0) {
174      console.info('remove listener');
175      this.listeners.splice(pos, 1);
176    }
177  }
178
179  notifyDataReload(): void {
180    this.listeners.forEach(listener => {
181      listener.onDataReloaded();
182    })
183  }
184
185  notifyDataAdd(index: number): void {
186    this.listeners.forEach(listener => {
187      listener.onDataAdd(index);
188    })
189  }
190
191  notifyDataChange(index: number): void {
192    this.listeners.forEach(listener => {
193      listener.onDataChange(index);
194    })
195  }
196
197  notifyDataDelete(index: number): void {
198    this.listeners.forEach(listener => {
199      listener.onDataDelete(index);
200    })
201  }
202
203  notifyDataMove(from: number, to: number): void {
204    this.listeners.forEach(listener => {
205      listener.onDataMove(from, to);
206    })
207  }
208}
209
210class MyDataSource extends BasicDataSource {
211  private dataArray: Array<string> = new Array(100).fill('test');
212
213  public totalCount(): number {
214    return this.dataArray.length;
215  }
216
217  public getData(index: number): string {
218    return this.dataArray[index];
219  }
220
221  public addData(index: number, data: string): void {
222    this.dataArray.splice(index, 0, data);
223    this.notifyDataAdd(index);
224  }
225
226  public pushData(data: string): void {
227    this.dataArray.push(data);
228    this.notifyDataAdd(this.dataArray.length - 1);
229  }
230}
231
232@Entry
233@Component
234struct MyComponent {
235  private data: MyDataSource = new MyDataSource();
236
237  build() {
238    Scroll() {
239      List() {
240        LazyForEach(this.data, (item: string, index: number ) => {
241          ListItem() {
242            Row() {
243              Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
244            }
245          }
246        })
247      }
248    }
249  }
250}
251```
252
253因此,此场景下建议设置List子组件的宽高。
254
255```ts
256class BasicDataSource implements IDataSource {
257  private listeners: DataChangeListener[] = [];
258  private originDataArray: string[] = [];
259
260  public totalCount(): number {
261    return 0;
262  }
263
264  public getData(index: number): string {
265    return this.originDataArray[index];
266  }
267
268  registerDataChangeListener(listener: DataChangeListener): void {
269    if (this.listeners.indexOf(listener) < 0) {
270      console.info('add listener');
271      this.listeners.push(listener);
272    }
273  }
274
275  unregisterDataChangeListener(listener: DataChangeListener): void {
276    const pos = this.listeners.indexOf(listener);
277    if (pos >= 0) {
278      console.info('remove listener')
279      this.listeners.splice(pos, 1);
280    }
281  }
282
283  notifyDataReload(): void {
284    this.listeners.forEach(listener => {
285      listener.onDataReloaded();
286    })
287  }
288
289  notifyDataAdd(index: number): void {
290    this.listeners.forEach(listener => {
291      listener.onDataAdd(index);
292    })
293  }
294
295  notifyDataChange(index: number): void {
296    this.listeners.forEach(listener => {
297      listener.onDataChange(index);
298    })
299  }
300
301  notifyDataDelete(index: number): void {
302    this.listeners.forEach(listener => {
303      listener.onDataDelete(index);
304    })
305  }
306
307  notifyDataMove(from: number, to: number): void {
308    this.listeners.forEach(listener => {
309      listener.onDataMove(from, to);
310    })
311  }
312}
313
314class MyDataSource extends BasicDataSource {
315  private dataArray: Array<string> = new Array(100).fill('test')
316
317  public totalCount(): number {
318    return this.dataArray.length;
319  }
320
321  public getData(index: number): string {
322    return this.dataArray[index];
323  }
324
325  public addData(index: number, data: string): void {
326    this.dataArray.splice(index, 0, data);
327    this.notifyDataAdd(index);
328  }
329
330  public pushData(data: string): void {
331    this.dataArray.push(data);
332    this.notifyDataAdd(this.dataArray.length - 1);
333  }
334}
335
336@Entry
337@Component
338struct MyComponent {
339  private data: MyDataSource = new MyDataSource();
340
341  build() {
342    Scroll() {
343      List() {
344        LazyForEach(this.data, (item: string, index: number) => {
345          ListItem() {
346            Text('item value: ' + item + (index + 1)).fontSize(20).margin(10)
347          }.width('100%')
348        })
349      }.width('100%').height(500)
350    }.backgroundColor(Color.Pink)
351  }
352}
353```
354
355![list1](figures/list1.gif)
356
357## 使用条件渲染替代显隐控制
358
359如下所示,开发者在使用visibility通用属性控制组件的显隐状态时,仍存在组件的重新创建过程,造成性能上的损耗。
360
361```ts
362@Entry
363@Component
364struct MyComponent {
365  @State isVisible: Visibility = Visibility.Visible;
366
367  build() {
368    Column() {
369      Button("显隐切换")
370        .onClick(() => {
371          if (this.isVisible == Visibility.Visible) {
372            this.isVisible = Visibility.None
373          } else {
374            this.isVisible = Visibility.Visible
375          }
376        })
377      Row().visibility(this.isVisible)
378        .width(300).height(300).backgroundColor(Color.Pink)
379    }.width('100%')
380  }
381}
382```
383
384要避免这一问题,可使用if条件渲染代替visibility属性变换,如下所示:
385
386```ts
387@Entry
388@Component
389struct MyComponent {
390  @State isVisible: boolean = true;
391
392  build() {
393    Column() {
394      Button("显隐切换")
395        .onClick(() => {
396          this.isVisible = !this.isVisible
397        })
398      if (this.isVisible) {
399        Row()
400          .width(300).height(300).backgroundColor(Color.Pink)
401      }
402    }.width('100%')
403  }
404}
405```
406
407![isVisible](figures/isVisible.gif)
408
409## 使用Column/Row替代Flex
410
411由于Flex容器组件默认情况下存在shrink导致二次布局,这会在一定程度上造成页面渲染上的性能劣化。
412
413```ts
414@Entry
415@Component
416struct MyComponent {
417  build() {
418    Flex({ direction: FlexDirection.Column }) {
419      Flex().width(300).height(200).backgroundColor(Color.Pink)
420      Flex().width(300).height(200).backgroundColor(Color.Yellow)
421      Flex().width(300).height(200).backgroundColor(Color.Grey)
422    }
423  }
424}
425```
426
427上述代码可将Flex替换为Column、Row,在保证实现的页面布局效果相同的前提下避免Flex二次布局带来的负面影响。
428
429```ts
430@Entry
431@Component
432struct MyComponent {
433  build() {
434    Column() {
435      Row().width(300).height(200).backgroundColor(Color.Pink)
436      Row().width(300).height(200).backgroundColor(Color.Yellow)
437      Row().width(300).height(200).backgroundColor(Color.Grey)
438    }
439  }
440}
441```
442
443![flex1](figures/flex1.PNG)
444
445## 减少应用滑动白块
446
447应用通过增大List/Grid控件的cachedCount参数,调整UI的加载范围。cachedCount表示屏幕外List/Grid预加载item的个数。
448如果需要请求网络图片,可以在item滑动到屏幕显示之前,提前下载好内容,从而减少滑动白块。
449如下是使用cachedCount参数的例子:
450
451```ts
452@Entry
453@Component
454struct MyComponent {
455  private source: MyDataSource = new MyDataSource();
456
457  build() {
458    List() {
459      LazyForEach(this.source, (item:string) => {
460        ListItem() {
461          Text("Hello" + item)
462            .fontSize(50)
463            .onAppear(() => {
464              console.log("appear:" + item)
465            })
466        }
467      })
468    }.cachedCount(3) // 扩大数值appear日志范围会变大
469  }
470}
471
472class MyDataSource implements IDataSource {
473  data: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
474
475  public totalCount(): number {
476    return this.data.length
477  }
478
479  public getData(index: number): number {
480    return this.data[index]
481  }
482
483  registerDataChangeListener(listener: DataChangeListener): void {
484  }
485
486  unregisterDataChangeListener(listener: DataChangeListener): void {
487  }
488}
489```
490![list2](figures/list2.gif)
491
492**使用说明:**
493cachedCount的增加会增大UI的cpu、内存开销。使用时需要根据实际情况,综合性能和用户体验进行调整。