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