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 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 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