• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 减少首帧绘制时的冗余操作
2
3------
4
5## 应用冷启动与加载绘制首页
6
7应用冷启动即当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用。
8
9OpenHarmony的应用冷启动过程大致可分成以下四个阶段:应用进程创建&初始化、Application&Ability初始化、Ability生命周期、**加载绘制首页**。
10
11![](figures/ColdStart.png)
12
13**加载绘制首页**不仅是应用冷启动的四个阶段之一,还是首帧绘制最重要的阶段。而它可以分为三个阶段:加载页面、测量和布局、渲染。本文从这三个阶段入手,分成下面三个场景进行案例优化。
14
15![](figures/Render-FirstFrame.png)
16
17## 减少加载页面时间
18
19减少加载页面时间可以通过按需加载、减少自定义组件生命周期耗时两种方法来实现。
20
21#### 按需加载
22
23按需加载可以避免一次性初始化和加载所有元素,从而使首帧绘制时加载页面阶段的创建列表元素时间大大减少,从而提升性能表现。
24
25**案例:每一个列表元素都被初始化和加载,为了突出效果,方便观察,设定数组中的元素有10000个,使其在加载页面阶段创建列表内元素耗时大大增加。**
26
27```ts
28@Entry
29@Component
30struct AllLoad {
31  @State arr: String[] = Array.from(Array(10000), (val,i) =>i.toString());
32  build() {
33    List() {
34      ForEach(this.arr, (item: string) => {
35        ListItem() {
36          Text(`item value: ${item}`)
37            .fontSize(20)
38            .margin({ left: 10 })
39        }
40      }, (item: string) => item.toString())
41    }
42  }
43}
44```
45
46**优化:LazyForEach替换ForEach,避免一次性初始化和加载所有元素。**
47
48```ts
49class BasicDataSource implements IDataSource {
50  private listeners: DataChangeListener[] = []
51
52  public totalCount(): number {
53    return 0
54  }
55
56  public getData(index: number): any {
57    return undefined
58  }
59
60  // 注册数据改变的监听器
61  registerDataChangeListener(listener: DataChangeListener): void {
62    if (this.listeners.indexOf(listener) < 0) {
63      console.info('add listener')
64      this.listeners.push(listener)
65    }
66  }
67
68  // 注销数据改变的监听器
69  unregisterDataChangeListener(listener: DataChangeListener): void {
70    const pos = this.listeners.indexOf(listener);
71    if (pos >= 0) {
72      console.info('remove listener')
73      this.listeners.splice(pos, 1)
74    }
75  }
76
77  // 通知组件重新加载所有数据
78  notifyDataReload(): void {
79    this.listeners.forEach(listener => {
80      listener.onDataReloaded()
81    })
82  }
83
84  // 通知组件index的位置有数据添加
85  notifyDataAdd(index: number): void {
86    this.listeners.forEach(listener => {
87      listener.onDataAdd(index)
88    })
89  }
90
91  // 通知组件index的位置有数据有变化
92  notifyDataChange(index: number): void {
93    this.listeners.forEach(listener => {
94      listener.onDataChange(index)
95    })
96  }
97
98  // 通知组件删除index位置的数据并刷新LazyForEach的展示内容
99  notifyDataDelete(index: number): void {
100    this.listeners.forEach(listener => {
101      listener.onDataDelete(index)
102    })
103  }
104
105  // 通知组件数据有移动
106  notifyDataMove(from: number, to: number): void {
107    this.listeners.forEach(listener => {
108      listener.onDataMove(from, to)
109    })
110  }
111}
112
113class MyDataSource extends BasicDataSource {
114  private dataArray: string[] = Array.from(Array(10000), (val, i) => i.toString());
115
116  public totalCount(): number {
117    return this.dataArray.length
118  }
119
120  public getData(index: number): any {
121    return this.dataArray[index]
122  }
123
124  public addData(index: number, data: string): void {
125    this.dataArray.splice(index, 0, data)
126    this.notifyDataAdd(index)
127  }
128
129  public pushData(data: string): void {
130    this.dataArray.push(data)
131    this.notifyDataAdd(this.dataArray.length - 1)
132  }
133}
134
135@Entry
136@Component
137struct SmartLoad {
138  private data: MyDataSource = new MyDataSource()
139
140  build() {
141    List() {
142      LazyForEach(this.data, (item: string) => {
143        ListItem() {
144          Text(`item value: ${item}`)
145            .fontSize(20)
146            .margin({ left: 10 })
147        }
148      }, item => item)
149    }
150  }
151}
152```
153
154
155
156#### 减少自定义组件生命周期时间
157
158LoadPage阶段需要等待自定义组件生命周期aboutToAppear的高耗时任务完成, 导致LoadPage时间大量增加,阻塞主线程后续的布局渲染,所以自定义组件生命周期的耗时任务应当转为Worker线程任务,优先绘制页面,避免启动时阻塞在startWindowIcon页面。
159
160**案例:自定义组件生命周期存在高耗时任务,阻塞主线程布局渲染。**
161
162```ts
163@Entry
164@Component
165struct TaskSync {
166  @State private text: string = undefined;
167  private count: number = undefined;
168
169  aboutToAppear() {
170    this.text = 'hello world';
171    this.computeTask(); // 同步任务
172  }
173
174  build() {
175    Column({space: 10}) {
176      Text(this.text).fontSize(50)
177    }
178    .width('100%')
179    .height('100%')
180    .padding(10)
181  }
182
183  computeTask() {
184    this.count = 0;
185    while (this.count < 100000000) {
186      this.count++;
187    }
188    this.text = 'task complete';
189  }
190}
191```
192
193**优化:自定义组件生命周期的耗时任务转为Worker线程任务,优先绘制页面,再将Worker子线程结果发送到主线程并更新到页面。**
194
195```ts
196// TaskAsync.ets
197import worker from '@ohos.worker';
198
199@Entry
200@Component
201struct TaskAsync {
202  @State private text: string = undefined;
203  private workerInstance:worker.ThreadWorker
204
205  aboutToAppear() {
206    this.workerInstance = new worker.ThreadWorker("entry/ets/workers/worker.ts");
207    // 处理来自子线程的消息
208    this.workerInstance.onmessage = (message)=> {
209      console.info('message from worker: ' + JSON.stringify(message))
210      this.text = JSON.parse(JSON.stringify(message)).data
211      this.workerInstance.terminate()
212    }
213    this.text = 'hello world';
214    // 执行Worker线程任务
215    this.computeTaskAsync();
216  }
217
218  build() {
219    Column({space: 10}) {
220      Text(this.text).fontSize(50)
221    }
222    .width('100%')
223    .height('100%')
224    .padding(10)
225  }
226  private async computeTaskAsync(){
227    // 发送消息到子线程
228    this.workerInstance.postMessage('hello world')
229  }
230}
231```
232
233```ts
234// worker.ts
235import worker from '@ohos.worker';
236
237let parentPort = worker.workerPort;
238
239function computeTask(count) {
240  while (count < 100000000) {
241    count++;
242  }
243  return 'task complete'
244}
245// 处理来自主线程的消息
246parentPort.onmessage = function(message) {
247  console.info("onmessage: " + JSON.stringify(message))
248  // 发送消息到主线程
249  parentPort.postMessage(computeTask(0))
250}
251```
252
253
254
255## 减少布局时间
256
257减少布局时间可以通过异步加载和减少视图嵌套层次两种方法来实现。
258
259#### 异步加载
260
261同步加载的操作,使创建图像任务需要在主线程完成,页面布局Layout需要等待创建图像makePixelMap任务的执行,导致布局时间延长。相反,异步加载的操作,在其他线程完成,和页面布局Layout同时开始,且没有阻碍页面布局,所以页面布局更快,性能更好。但是,并不是所有的加载都必须使用异步加载,建议加载尺寸较小的本地图片时将syncLoad设为true,因为耗时较短,在主线程上执行即可。
262
263**案例:使用Image组件同步加载高分辨率图片,阻塞UI线程,增加了页面布局总时间。**
264
265```ts
266@Entry
267@Component
268struct SyncLoadImage {
269  @State arr: String[] = Array.from(Array(100), (val,i) =>i.toString());
270  build() {
271    Column() {
272      Row() {
273        List() {
274          ForEach(this.arr, (item: string) => {
275            ListItem() {
276              Image($r('app.media.4k'))
277                .border({ width: 1 })
278                .borderStyle(BorderStyle.Dashed)
279                .height(100)
280                .width(100)
281                .syncLoad(true)
282            }
283          }, (item: string) => item.toString())
284        }
285      }
286    }
287  }
288}
289```
290
291**优化:使用Image组件默认的异步加载方式加载图片,不阻塞UI线程,降低页面布局时间。**
292
293```ts
294@Entry
295@Component
296struct AsyncLoadImage {
297  @State arr: String[] = Array.from(Array(100), (val,i) =>i.toString());
298    build() {
299      Column() {
300        Row() {
301          List() {
302            ForEach(this.arr, (item: string) => {
303              ListItem() {
304                Image($r('app.media.4k'))
305                  .border({ width: 1 })
306                  .borderStyle(BorderStyle.Dashed)
307                  .height(100)
308                  .width(100)
309              }
310            }, (item: string) => item.toString())
311          }
312        }
313      }
314  }
315}
316```
317
318
319
320#### 减少视图嵌套层次
321
322视图的嵌套层次会影响应用的性能。通过减少不合理的容器组件,可以使布局深度降低,布局时间减少,优化布局性能,提升用户体验。
323
324**案例:通过Grid网格容器一次性加载1000个网格,并且额外使用3层Flex容器模拟不合理的深嵌套场景使布局时间增加。**
325
326```ts
327@Entry
328@Component
329struct Depth1 {
330  @State number: Number[] = Array.from(Array<number>(1000), (val, i) => i);
331  scroller: Scroller = new Scroller()
332
333  build() {
334    Column() {
335      Grid(this.scroller) {
336        ForEach(this.number, (item: number) => {
337          GridItem() {
338            Flex() {
339              Flex() {
340                Flex() {
341                  Text(item.toString())
342                    .fontSize(16)
343                    .backgroundColor(0xF9CF93)
344                    .width('100%')
345                    .height(80)
346                    .textAlign(TextAlign.Center)
347                    .border({width:1})
348                }
349              }
350            }
351          }
352        }, (item:string) => item)
353      }
354      .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
355      .columnsGap(0)
356      .rowsGap(0)
357      .size({ width: "100%", height: "100%" })
358    }
359  }
360}
361```
362
363**优化:通过Grid网格容器一次性加载1000个网格,去除额外的不合理的布局容器,降低布局时间。**
364
365```ts
366@Entry
367@Component
368struct Depth2 {
369  @State number: Number[] = Array.from(Array<number>(1000), (val, i) => i);
370  scroller: Scroller = new Scroller()
371
372  build() {
373    Column() {
374      Grid(this.scroller) {
375        ForEach(this.number, (item: number) => {
376          GridItem() {
377                  Text(item.toString())
378                    .fontSize(16)
379                    .backgroundColor(0xF9CF93)
380                    .width('100%')
381                    .height(80)
382                    .textAlign(TextAlign.Center)
383                    .border({width:1})
384          }
385        }, (item:string) => item)
386      }
387      .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
388      .columnsGap(0)
389      .rowsGap(0)
390      .size({ width: "100%", height: "100%" })
391    }
392  }
393}
394```
395
396
397
398## 减少渲染时间
399
400减少渲染时间可以通过条件渲染替代显隐控制的方法来实现。
401
402#### 条件渲染
403
404通过条件渲染替代显隐控制,首帧绘制时的渲染时间明显降低,从而提升性能表现。另外,即使组件处于隐藏状态,在页面刷新时仍存在重新创建过程,因此当对性能有严格要求时建议使用条件渲染代替。
405
406**案例:通过visibility属性控制当前组件显示或隐藏。**
407
408```ts
409@Entry
410@Component
411struct VisibilityExample {
412  build() {
413    Column() {
414      Column() {
415        // 隐藏不参与占位
416        Text('None').fontSize(9).width('90%').fontColor(0xCCCCCC)
417        Row().visibility(Visibility.None).width('90%').height(80).backgroundColor(0xAFEEEE)
418
419        // 隐藏参与占位
420        Text('Hidden').fontSize(9).width('90%').fontColor(0xCCCCCC)
421        Row().visibility(Visibility.Hidden).width('90%').height(80).backgroundColor(0xAFEEEE)
422
423        // 正常显示,组件默认的显示模式
424        Text('Visible').fontSize(9).width('90%').fontColor(0xCCCCCC)
425        Row().visibility(Visibility.Visible).width('90%').height(80).backgroundColor(0xAFEEEE)
426      }.width('90%').border({ width: 1 })
427    }.width('100%').margin({ top: 5 })
428  }
429}
430```
431
432**优化:通过条件渲染替代显隐控制。**
433
434```ts
435@Entry
436@Component
437struct IsVisibleExample {
438  @State isVisible : boolean = true;
439
440  build() {
441    Column(){
442      Column() {
443        //不渲染即达到隐藏不参与占位
444        Text('None').fontSize(9).width('90%').fontColor(0xCCCCCC)
445        if (!this.isVisible) {
446          Row().width('90%').height(80).backgroundColor(0xAFEEEE)
447        }
448
449        // 隐藏参与占位无法通过条件渲染实现
450
451        // 渲染即正常占位显示
452        Text('Visible').fontSize(9).width('90%').fontColor(0xCCCCCC)
453        if (this.isVisible){
454          Row().width('90%').height(80).backgroundColor(0xAFEEEE)
455        }
456      }.width('90%').border({ width: 1 })
457    }.width('100%').margin({ top: 5 })
458  }
459}
460```
461