1# Reducing Redundant Operations on First Frame Drawing
2
3------
4
5## Application Cold Start and Home Page Loading and Drawing
6
7A code start is a startup mode where an application is started from scratch – the background does not have a process of the application, and therefore the system creates a new process and allocates it to the application.
8
9The cold start process can be divided into four phases: application process creation and initialization, application and ability initialization, ability lifecycle, and home page loading and drawing, as shown below.
10
11![](figures/ColdStart.png)
12
13Home page loading and drawing is not only one of the four phases of application cold start, but also the most important phase of first frame drawing. It can be further divided into three phases: page load, measurement and layout, and render. This topic explores how application performance can be improved at these phases.
14
15![](figures/Render-FirstFrame.png)
16
17## Reducing Page Load Time
18
19You can reduce your page load time by loading pages on demand or reducing the lifecycle time of custom components.
20
21#### On-demand Loading
22
23On-demand loading, as its name implies, loads resources only when they are needed. It avoids initializing and loading all elements at a time. In this way, the time required for creating list elements is reduced, allowing pages to load faster.
24
25**Negative example**: There is a list with 10,000 elements (the sheer number is intended to make it easier to observe the benefits of on-demand loading). If each of the list element is initialized and loaded during page load, the page load time will be excessively long.
26
27```ts
28@Entry
29@Component
30struct AllLoad {
31  @State arr: String[] = Array.from(Array<string>(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**Optimization**: Replace **ForEach** with **LazyForEach** to avoid initializing and loading all elements at a time.
47
48```ts
49class BasicDataSource implements IDataSource {
50  private listeners: DataChangeListener[] = [];
51  private originDataArray: string[] = [];
52
53  public totalCount(): number {
54    return 0;
55  }
56
57  public getData(index: number): string {
58    return this.originDataArray[index]
59  }
60
61  // Register a listener for data changes.
62  registerDataChangeListener(listener: DataChangeListener): void {
63    if (this.listeners.indexOf(listener) < 0) {
64      console.info('add listener')
65      this.listeners.push(listener)
66    }
67  }
68
69  // Deregister the listener for data changes.
70  unregisterDataChangeListener(listener: DataChangeListener): void {
71    const pos = this.listeners.indexOf(listener);
72    if (pos >= 0) {
73      console.info('remove listener')
74      this.listeners.splice(pos, 1)
75    }
76  }
77
78  // Invoked when all data is reloaded.
79  notifyDataReload(): void {
80    this.listeners.forEach(listener => {
81      listener.onDataReloaded()
82    })
83  }
84
85  // Invoked when data is added to the position indicated by the specified index.
86  notifyDataAdd(index: number): void {
87    this.listeners.forEach(listener => {
88      listener.onDataAdd(index)
89    })
90  }
91
92  // Invoked when data in the position indicated by the specified index is changed.
93  notifyDataChange(index: number): void {
94    this.listeners.forEach(listener => {
95      listener.onDataChange(index)
96    })
97  }
98
99  // Invoked when data is deleted from the position indicated by the specified index. LazyForEach will update the displayed content accordingly.
100  notifyDataDelete(index: number): void {
101    this.listeners.forEach(listener => {
102      listener.onDataDelete(index)
103    })
104  }
105
106  // Invoked when data is moved.
107  notifyDataMove(from: number, to: number): void {
108    this.listeners.forEach(listener => {
109      listener.onDataMove(from, to)
110    })
111  }
112}
113
114class MyDataSource extends BasicDataSource {
115  private dataArray: string[] = Array.from(Array<string>(10000), (val, i) => i.toString());
116
117  public totalCount(): number {
118    return this.dataArray.length
119  }
120
121  public getData(index: number): string {
122    return this.dataArray[index]
123  }
124
125  public addData(index: number, data: string): void {
126    this.dataArray.splice(index, 0, data)
127    this.notifyDataAdd(index)
128  }
129
130  public pushData(data: string): void {
131    this.dataArray.push(data)
132    this.notifyDataAdd(this.dataArray.length - 1)
133  }
134}
135
136@Entry
137@Component
138struct SmartLoad {
139  private data: MyDataSource = new MyDataSource()
140
141  build() {
142    List() {
143      LazyForEach(this.data, (item: string) => {
144        ListItem() {
145          Text(`item value: ${item}`)
146            .fontSize(20)
147            .margin({ left: 10 })
148        }
149      }, (item:string) => item)
150    }
151  }
152}
153```
154
155
156
157#### Reducing Lifecycle of Custom Components
158
159The custom component lifecycle **aboutToAppear** is time-consuming and, if not well managed, may greatly increase page load times and even block subsequent layout rendering of the main thread. Therefore, the lifecycle needs to be converted to a worker thread task so that the page can be rendered without waiting for the lifecycle to be completed first and the main thread will not be blocked at the startWindowIcon page.
160
161**Negative example**: The time-consuming lifecycle of a custom component blocks the layout rendering of the main thread.
162
163```ts
164@Entry
165@Component
166struct TaskSync {
167  @State private text: string = "";
168  private count: number = 0;
169
170  aboutToAppear() {
171    this.text = 'hello world';
172    this.computeTask(); // Synchronization task
173  }
174
175  build() {
176    Column({space: 10}) {
177      Text(this.text).fontSize(50)
178    }
179    .width('100%')
180    .height('100%')
181    .padding(10)
182  }
183
184  computeTask() {
185    this.count = 0;
186    while (this.count < 100000000) {
187      this.count++;
188    }
189    this.text = 'task complete';
190  }
191}
192```
193
194**Optimization**: Convert the time-consuming lifecycle to a worker thread task. In this way, the page is drawn first, and then the worker thread result is sent to the main thread and updated to the page.
195
196```ts
197// TaskAsync.ets
198import worker from '@ohos.worker';
199
200@Entry
201@Component
202struct TaskAsync {
203  @State private text: string = "";
204  private workerInstance:worker.ThreadWorker = new worker.ThreadWorker("entry/ets/workers/worker.ts");
205
206  aboutToAppear() {
207    // Process messages from the worker thread.
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    // Execute the worker thread task.
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    // Send a message to the worker thread.
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: number) {
240  while (count < 100000000) {
241    count++;
242  }
243  return 'task complete'
244}
245// Process messages from the main thread.
246parentPort.onmessage = (message) => {
247  console.info("onmessage: " + JSON.stringify(message));
248  // Send a message to the main thread.
249  parentPort.postMessage(computeTask(0));
250}
251```
252
253
254
255## Reducing Layout Time
256
257Reducing layout time can be achieved by asynchronous loading and reduced nesting.
258
259#### Asynchronous Loading
260
261During synchronous loading, images are loaded in the main thread. This means that, page layout needs to wait until the main thread has completed the **makePixelMap** task, resulting in long page layout times. On the contrary, during asynchronous loading, images are loaded in other threads while page layout is executed in the main thread. In this way, no blocking occurs, leading to faster page layout and better performance. However, not all image loading must be asynchronous loading. For small local images that are fast to load, synchronous loading (with **syncLoad** set to **true**) is preferrable.
262
263**Negative example**: The **Image** component is used to synchronously load high-resolution images, blocking the UI thread and increasing the total page layout time.
264
265```ts
266@Entry
267@Component
268struct SyncLoadImage {
269  @State arr: String[] = Array.from(Array<string>(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**Optimization**: The **Image** component is loaded in the default asynchronous loading mode, which avoids blocking the UI thread and reduces the page layout time.
292
293```ts
294@Entry
295@Component
296struct AsyncLoadImage {
297  @State arr: String[] = Array.from(Array<string>(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#### Reducing Nesting
321
322The view hierarchy can significantly affect application performance. Flatter view hierarchies can bring faster page layout and better layout performance. Therefore, whenever possible, reduce nesting and eliminate misplaced container components.
323
324**Negative example**: A **Grid** container is used to load 1000 grids at a time, and additional three-layer **Flex** containers are used , resulting in an unnecessarily deeply nested structure.
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**Optimization**: A **Grid** container is used to load 1000 grids at a time, but this time no unnecessary containers are used, which leads to faster layout.
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## Reducing Render Time
399
400You can reduce render time by replacing visibility control with conditional rendering.
401
402#### Conditional Rendering
403
404Replacing visibility control with conditional rendering can significantly reduce the render time during first frame drawing. With visibility control, even if a component is hidden, it still needs to be re-created when the page is re-rendered. Therefore, in scenarios where performance is critical, you are advised to use conditional rendering instead.
405
406**Negative example**: The **visibility** attribute is used to display or hide the component.
407
408```ts
409@Entry
410@Component
411struct VisibilityExample {
412  build() {
413    Column() {
414      Column() {
415        // The component is hidden and does not take up space in the layout.
416        Text('None').fontSize(9).width('90%').fontColor(0xCCCCCC)
417        Row().visibility(Visibility.None).width('90%').height(80).backgroundColor(0xAFEEEE)
418
419        // The component is hidden but takes up space in the layout.
420        Text('Hidden').fontSize(9).width('90%').fontColor(0xCCCCCC)
421        Row().visibility(Visibility.Hidden).width('90%').height(80).backgroundColor(0xAFEEEE)
422
423        // The component is visible, which is the default display mode.
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**Optimization**: Conditional rendering is used to replace visibility control.
433
434```ts
435@Entry
436@Component
437struct IsVisibleExample {
438  @State isVisible : boolean = true;
439
440  build() {
441    Column(){
442      Column() {
443        // The component is not rendered. In this way, it is hidden and does not take up space in the layout.
444        Text('None').fontSize(9).width('90%').fontColor(0xCCCCCC)
445        if (!this.isVisible) {
446          Row().width('90%').height(80).backgroundColor(0xAFEEEE)
447        }
448
449        // Conditional rendering cannot be used to hide the component with it still taking up space in the layout.
450
451        // The component is rendered. In this way, it becomes visible.
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