1# 应用开发性能优化入门引导 2 3## 概述 4 5在开发应用时,优化应用性能是至关重要的。本文将介绍应用开发过程中常见的一些性能问题,并提供相应的解决方案,配合相关参考示例,帮助开发者解决大部分性能问题。 6 7应用性能分析的方法划分为了**性能分析四要素**,下面将介绍如何使用性能分析四要素,解决应用开发过程中的性能问题。 8 9* **第一要素:合理使用并行化、预加载和缓存**,需要合理地使用并行化、预加载和缓存等方法,例如使用多线程并发、异步并发、Web预加载等能力,提升系统资源利用率,减少主线程负载,加快应用的启动速度和响应速度。 10 11* **第二要素:尽量减少布局的嵌套层数**,在进行页面布局开发时,应该去除冗余的布局嵌套,使用相对布局、绝对定位、自定义布局、Grid、GridRow等扁平化布局,减少布局的嵌套层数,避免系统绘制更多的布局组件,达到优化性能、减少内存占用的目的。 12 13* **第三要素:合理管理状态变量**,应该合理地使用状态变量,精准控制组件的更新范围,控制状态变量关联组件数量,控制对象级状态变量的成员变量关联组件数,减少系统的组件渲染负载,提升应用流畅度。 14 15* **第四要素:合理使用系统接口,避免冗余操作**,应该合理使用系统的高频回调接口,删除不必要的Trace和日志打印,避免注册系统冗余回调,减少系统开销。 16 17## 第一要素:合理使用并行化、预加载和缓存 18 19需要合理地使用并行化、预加载和缓存等方法,提升系统资源利用率,减少主线程负载,加快应用的启动速度和响应速度。 20 21### 使用并行化提升启动速度 22 23自定义组件创建完成之后,在build函数执行之前,将先执行[aboutToAppear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear)生命周期回调函数。此时若在该函数中执行耗时操作,将阻塞UI渲染,增加UI主线程负担。因此,应尽量避免在自定义组件的生命周期内执行高耗时操作。在aboutToAppear生命周期函数内建议只做当前组件的初始化逻辑,对于不需要等待结果的高耗时任务,可以使用多线程处理该任务,通过并发的方式避免主线程阻塞;也可以把耗时操作改为异步并发或延后处理,保证主线程优先处理组件绘制逻辑。 24 25#### 使用多线程执行耗时操作 26 27在日常开发过程中经常会碰到这样的问题:主页的开发场景中有多个Tab页展示不同内容,在首次加载完主页后,切换到第二个Tab页时需要加载和处理网络数据,导致第二个Tab页的页面显示较慢,有较大的完成时延。 28 29碰到此类问题,可以在生命周期aboutToAppear中,使用多线程并发、[高效并发编程](efficient-concurrent-programming.md)、[多线程能力场景化示例实践](multi_thread_capability.md)的方法执行第二个Tab页的网络数据访问解析、数据加载等耗时操作,既可以提前完成数据加载,也不会影响主线程UI绘制和渲染。 30 31使用TaskPool进行耗时操作的示例代码如下: 32 33```typescript 34import taskpool from '@ohos.taskpool'; 35 36aboutToAppear() { 37 // 在生命周期中,使用TaskPool加载和解析网络数据 38 this.requestByTaskPool(); 39} 40 41@Concurrent 42getInfoFromHttp(): string[] { 43 // 从网络加载数据 44 return http.request(); 45} 46 47requestByTaskPool(): void { 48 // 创建任务项 49 let task: taskpool.Task = new taskpool.Task(this.getInfoFromHttp); 50 try { 51 // 执行网络加载函数 52 taskpool.execute(task, taskpool.Priority.HIGH).then((res: string[]) => { 53 }); 54 } catch (err) { 55 logger.error(TAG, "failed, " + (err as BusinessError).toString()); 56 } 57} 58``` 59 60其他多线程并发相关文章: 61 62* [利用native的方式实现跨线程调用](native-threads-call-js.md) 63 64 65#### 使用异步执行耗时操作 66 67问题:在aboutToAppear生命周期函数中,运行了业务数据解析和处理等耗时操作,影响了上一页面点击跳转该页面的响应时延。 68 69可以把耗时操作的执行从同步执行改为异步或者延后执行,[提升应用冷启动速度](improve-application-cold-start-speed.md),比如使用setTimeOut执行耗时操作,示例如下: 70 71```typescript 72aboutToAppear() { 73 // 在生命周期中,使用异步处理数据,延时大小视情况确定 74 setTimeout(() => { 75 this.workoutResult(); 76 }, 1000) 77} 78 79workoutResult(): string[] { 80 // 处理需要展示的业务数据 81 let data: Data[] = []; 82 for(let i = 1; i < 100; i++) { 83 result += data[i]; 84 } 85 return result; 86} 87``` 88 89### 使用预加载提升页面启动和响应速度 90 91应该合理使用系统的预加载能力,例如Web组件的预连接、预加载、预渲染,使用List、Swiper、Grid、WaterFlow等组件的cachedCount属性实现预加载,使用条件渲染实现预加载)等,提升页面的启动和响应速度。 92 93#### 使用Web组件的预连接、预加载、预渲染能力 94 95当遇到Web页面加载慢的场景,可以使用Web组件的预连接、预加载、预渲染能力,使用[Web组件开发性能提升指导](performance-web-import.md),在应用空闲时间提前进行Web引擎初始化和页面加载,提升下一页面的启动和响应速度。 96 97示例代码如下: 98 99```typescript 100import webview from '@ohos.web.webview'; 101 102preload() { 103 // Web组件引擎初始化 104 webview.WebviewController.initializeWebEngine(); 105 // 启动预连接,连接地址为即将打开的网址 106 webview.WebviewController.prepareForPageLoad('https://www.example.com', true, 2); 107} 108``` 109 110#### 使用cachedCount属性实现预加载 111 112推荐在使用List、Swiper、Grid、WaterFlow等组件时,配合使用cachedCount属性实现预加载,详情指导在[WaterFlow高性能开发指导](waterflow_optimization.md)、[Swiper高性能开发指导](swiper_optimization.md)、[Grid高性能开发指导](grid_optimization.md)、[列表场景性能提升实践](list-perf-improvment.md),示例代码如下所示: 113 114```typescript 115 private source: MyDataSource = new MyDataSource(); 116 117 build() { 118 List() { 119 LazyForEach(this.source, item => { 120 ListItem() { 121 Text("Hello" + item) 122 .fontSize(50) 123 .onAppear(() => { 124 console.info("appear:" + item) 125 }) 126 } 127 }) 128 }.cachedCount(3) // 扩大数值appear日志范围会变大 129 } 130``` 131 132#### 使用条件渲染实现预加载 133 134问题:页面布局复杂度较高,导致跳转该页面的响应时延较高。 135 136可以使用条件渲染的方式进行[合理选择条件渲染和显隐控制](proper-choice-between-if-and-visibility.md),添加页面的简单骨架图作为默认展示页面,等数据加载完成后再显示最终的复杂布局,加快点击响应速度。 137 138示例代码如下: 139 140```typescript 141import skeletonComponent from "./skeletonComponent"; 142import businessComponent from "./businessComponent"; 143 144@State isInitialized: boolean = false; 145 146build() { 147 // 当数据未就位时展示骨架图,提升点击响应速度,减少页面渲染时间 148 if(!this.isInitialized) { 149 // 网络数据未获取前使用骨架图 150 skeletonComponent(); 151 } else { 152 // 数据获取后再刷新显示内容 153 businessComponent(); 154 } 155} 156``` 157 158### 使用缓存提升启动速度和滑动帧率 159 160在列表场景中,推荐使用LazyForEach+组件复用+缓存列表项的能力,替代Scroll/ForEach实现滚动列表场景的实现,加快页面启动速度,提升滑动帧率;在一些属性动画的场景下,可以使用renderGroup缓存提升属性动画性能;也可以使用显隐控制对页面进行缓存,加快页面的显示响应速度。 161 162#### 组件复用 163 164应用框架提供了组件复用能力,可复用组件从组件树上移除时,会进入到一个回收缓存区。后续创建新组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。 165 166若业务实现中存在以下场景,并成为UI线程的帧率瓶颈,推荐使用组件复用,具体指导在[组件复用实践](component-recycle.md)、[列表场景性能提升实践](list-perf-improvment.md)、[组件复用总览](component-reuse-overview.md): 167 168* 列表滚动(本例中的场景):当应用需要展示大量数据的列表,并且用户进行滚动操作时,频繁创建和销毁列表项的视图可能导致卡顿和性能问题。在这种情况下,使用列表组件的组件复用机制可以重用已经创建的列表项视图,提高滚动的流畅度。 169* 动态布局更新:如果应用中的界面需要频繁地进行布局更新,例如根据用户的操作或数据变化动态改变视图结构和样式,重复创建和销毁视图可能导致频繁的布局计算,影响帧率。在这种情况下,使用组件复用可以避免不必要的视图创建和布局计算,提高性能。 170* 地图渲染:在地图渲染这种场景下,频繁创建和销毁数据项的视图可能导致性能问题。使用组件复用可以重用已创建的视图,只更新数据的内容,减少视图的创建和销毁,能有效提高性能。 171 172示例代码如下: 173 174```typescript 175// xxx.ets 176class MyDataSource implements IDataSource { 177 private dataArray: string[] = []; 178 private listener: DataChangeListener | undefined; 179 // ... 180} 181 182@Entry 183@Component 184struct MyComponent { 185 private data: MyDataSource = new MyDataSource(); 186 187 aboutToAppear() { 188 for (let i = 0; i < 1000; i++) { 189 this.data.pushData(i.toString()); 190 } 191 } 192 193 build() { 194 List({ space: 3 }) { 195 LazyForEach(this.data, (item: string) => { 196 ListItem() { 197 ReusableChildComponent({ item: item }) 198 } 199 }, (item: string) => item) 200 } 201 .width('100%') 202 .height('100%') 203 } 204} 205 206@Reusable 207@Component 208struct ReusableChildComponent { 209 @State item: string = '' 210 // 复用时触发的生命周期 211 aboutToReuse(params: ESObject) { 212 this.item = params.item; 213 } 214 215 build() { 216 Row() { 217 Text(this.item) 218 .fontSize(20) 219 .margin({ left: 10 }) 220 }.margin({ left: 10, right: 10 }) 221 } 222} 223``` 224 225#### 使用renderGroup缓存提升属性动画性能 226 227页面响应时,可能大量使用属性动画和转场动画,当复杂度达到一定程度之后,就有可能出现卡顿的情况。[renderGroup](reasonable-using-renderGroup.md)是组件通用方法,它代表了渲染绘制的一个组合。 228 229具体原理是在首次绘制组件时,若组件被标记为启用renderGroup状态,将对组件及其子组件进行离屏绘制,将绘制结果合并保存到缓存中。此后当需要重新绘制相同组件时,就会优先使用缓存而不必重新绘制了,从而降低绘制负载,进而加快响应速度。 230 231示例代码如下: 232 233```typescript 234// Index.ets 235import { IconItem } from './IconItem'; 236 237// IconItem相关数据 238class IconItemSource { 239 image: string | Resource = '' 240 text: string | Resource = '' 241 // ... 242} 243 244@Entry 245@Component 246struct Index { 247 private iconItemSourceList: IconItemSource[] = []; 248 249 aboutToAppear() { 250 // 遍历添加IconItem的数据 251 this.iconItemSourceList.push( 252 new IconItemSource($r('app.media.img1'), `label1`), 253 new IconItemSource($r('app.media.img2'), `label2`), 254 new IconItemSource($r('app.media.img3'), `label3`) 255 ); 256 } 257 258 build() { 259 Column() { 260 // IconItem放置在grid内 261 GridRow() { 262 ForEach(this.iconItemSourceList, (item: IconItemSource) => { 263 GridCol() { 264 IconItem({ image: item.image, text: item.text }) 265 .transition(TransitionEffect.scale({ x: 0, y: 0 }) 266 .animation({ delay: 1000, duration: 1000 }) 267 .combine(TransitionEffect.rotate({ z: 1, angle: 180 }) 268 .animation({ duration: 1000 })) 269 ) 270 } 271 }) 272 } 273 } 274 } 275} 276 277// IconItem.ets 278@Component 279export struct IconItem { 280 build() { 281 Flex() { 282 Image(this.image) 283 Text(this.text) 284 } 285 // 在IconItem内开启renderGroup 286 .renderGroup(true) 287 } 288} 289``` 290 291#### 使用显隐控制进行页面缓存 292 293控制元素显示与隐藏是一种常见的场景,使用Visibility.None、if条件判断等都能够实现该效果。其中if条件判断控制的是组件的创建、布局阶段,Visibility属性控制的是元素在布局阶段是否参与布局渲染。使用时如果使用的方式不当,将引起性能上的问题。 294如果会频繁响应显示与隐藏的交互效果,建议使用切换Visibility.None和Visibility.Visible来[合理控制元素显示与隐藏](proper-choice-between-if-and-visibility.md),在组件无需展示的时候进行缓存,提高性能。 295 296示例代码如下: 297 298```typescript 299@State isVisible: boolean = true; 300 301build() { 302 Column() { 303 Button("Switch visible and hidden").onClick(() => { 304 this.isVisible = !this.isVisible; 305 }) 306 Stack() { 307 Scroll() { 308 Column() { 309 Image($r('app.media.icon')) 310 } 311 }.visibility(this.isVisible ? Visibility.Visible : Visibility.None)// 使用显隐控制切换,不会频繁创建与销毁组件 312 } 313 } 314} 315``` 316 317## 第二要素:尽量减少布局的嵌套层数 318 319在进行页面布局开发时,应该去除冗余的布局嵌套,使用相对布局、绝对定位、自定义布局、Grid、GridRow等扁平化布局,减少布局的嵌套层数,避免系统绘制更多的布局组件,达到[优化布局性能](reduce-view-nesting-levels.md)、减少内存占用的目的。 320 321### 移除冗余节点 322 323应该删除冗余的布局嵌套,例如build最外层的无用容器嵌套、无用的Stack或Column嵌套等,减少布局层数。 324 325#### 删除无用的Stack/Column/Row嵌套 326 327例如可能会在Row容器包含一个同样也是Row容器的子级。这种嵌套实际是多余的,并且会给布局层次结构造成不必要的开销。示例代码如下: 328 329```typescript 330// 反例 331Row() { 332 Row() { 333 Text() 334 Text() 335 } 336 Text() 337} 338 339// 正例 340Row() { 341 Text() 342 Text() 343 Text() 344} 345``` 346 347#### 删除build函数中最外层无用容器嵌套 348 349在开发过程中,布局的实现往往嵌套使用大量的自定义组件,build中冗余的最外层无用容器会大大增强嵌套层级,应该删除。 350 351反例代码如下: 352 353```typescript 354@Entry 355@Component 356struct ComponentA { 357 build() { 358 Column() { 359 ComponentB(); 360 } 361 } 362} 363 364@Component 365struct ComponentB { 366 build() { 367 Column() { 368 Text(''); 369 } 370 } 371} 372``` 373 374正例代码如下: 375 376```typescript 377@Entry 378@Component 379struct ComponentA { 380 build() { 381 Column() { 382 ComponentB(); 383 } 384 } 385} 386 387@Component 388struct ComponentB { 389 build() { 390 Text(''); 391 } 392} 393``` 394 395### 使用扁平化布局减少节点数 396 397#### 使用Column/Row替代Flex构建线性布局 398 399由于Flex本身带来的二次布局的影响,Flex的性能明显低于Column和Row容器,因此推荐使用Column/Row替代Flex构建线性布局,具体指导在[Flex布局性能提升使用指导](flex-development-performance-boost.md)。 400 401反例代码如下: 402 403```typescript 404@Entry 405@Component 406struct MyComponent { 407 build() { 408 Flex({ direction: FlexDirection.Column }) { 409 Flex().width(300).height(200).backgroundColor(Color.Pink) 410 Flex().width(300).height(200).backgroundColor(Color.Yellow) 411 Flex().width(300).height(200).backgroundColor(Color.Grey) 412 } 413 } 414} 415``` 416 417正例代码如下: 418 419```typescript 420@Entry 421@Component 422struct MyComponent { 423 build() { 424 Column() { 425 Row().width(300).height(200).backgroundColor(Color.Pink) 426 Row().width(300).height(200).backgroundColor(Color.Yellow) 427 Row().width(300).height(200).backgroundColor(Color.Grey) 428 } 429 } 430} 431``` 432 433#### 使用Flex、List、Grid、RelativeContainer、绝对布局和自定义布局等构建复杂布局 434 435复杂布局提供了场景化的能力,[优化布局性能](reduce-view-nesting-levels.md)可解决一种或者多种布局场景: 436 437* 使用Flex构建弹性布局; 438* List既具备线性布局的特点,同时支持懒加载和滑动的能力; 439* Grid/GridItem提供了宫格布局的能力,同时也支持懒加载和滑动能力; 440* RelativeContainer是一种相对布局,通过描述各个内容组件间相互关系来指导内容元素的布局过程,可从横纵两个方面进行布局描述,是一种二维布局算法。 441 442反例代码如下: 443 444```typescript 445@Entry 446@Component 447struct AspectRatioExample12 { 448 @State children: Number[] = Array.from(Array<number>(900), (v, k) => k); 449 450 build() { 451 Scroll() { 452 Grid() { 453 ForEach(this.children, (item: Number[]) => { 454 GridItem() { 455 Stack() { 456 Stack() { 457 Stack() { 458 Text(item.toString()) 459 }.size({ width: "100%"}) 460 }.backgroundColor(Color.Yellow) 461 }.backgroundColor(Color.Pink) 462 } 463 }, (item: string) => item) 464 } 465 .columnsTemplate('1fr 1fr 1fr 1fr') 466 .columnsGap(0) 467 .rowsGap(0) 468 .size({ width: "100%", height: "100%" }) 469 } 470 } 471} 472``` 473 474正例代码如下: 475 476```typescript 477@Entry 478@Component 479struct AspectRatioExample11 { 480 @State children: Number[] = Array.from(Array<number>(900), (v, k) => k); 481 482 build() { 483 Scroll() { 484 Grid() { 485 ForEach(this.children, (item: Number[]) => { 486 GridItem() { 487 Text(item.toString()) 488 }.backgroundColor(Color.Yellow) 489 }, (item: string) => item) 490 } 491 .columnsTemplate('1fr 1fr 1fr 1fr') 492 .columnsGap(0) 493 .rowsGap(0) 494 .size({ width: "100%", height: "100%" }) 495 } 496 } 497} 498``` 499 500## 第三要素:合理管理状态变量 501 502应该合理地使用状态变量,[精准控制组件的更新范围](precisely-control-render-scope.md),控制状态变量关联组件数量上限,控制对象级状态变量的成员变量关联组件数,减少系统的组件渲染负载,提升应用流畅度。 503 504### 精准控制组件的更新范围 505 506在复杂页面开发的场景下,精准控制组件更新的范围对提高应用运行性能尤为重要。应该避免状态变量的滥用引起的容器组件的刷新,进而影响帧率。 507 508#### 使用指定宽高的容器限制刷新范围 509 510当在一个同时指定宽高的容器里改变容器内部的布局,那么只会在该容器内部做布局和测量更新,不会扩散影响到容器外面的组件。 511 512反例代码如下: 513 514```typescript 515struct StackExample { 516 @State isVisible: boolean = true; 517 private data: number[] = []; 518 519 aboutToAppear() { 520 for (let i: number = 0; i < Constants.IMAGE_TOTAL_NUM; i++) { 521 this.data.push(i); 522 } 523 } 524 525 build() { 526 Column() { 527 Button('Switch Hidden and Show').onClick(() => { 528 this.isVisible = !this.isVisible; 529 }) 530 531 Stack() { 532 if (this.isVisible) { 533 Text('New Page').width(100).height(30).backgroundColor(0xd2cab3) 534 } 535 }.width(100) // 本案例以Stack容器为例,只指定了宽,会触发父容器组件重新布局计算,引起ForEach中文本测量。 536 537 ForEach(this.data, (item: number) => { // 由于Stack容器没有同时指定宽高,会扩散影响到这一层,引起Text的测量更新。 538 Text(`Item value: ${item}`) 539 .fontSize($r('app.integer.font_size_20')) 540 .width($r('app.string.layout_100_percent')) 541 .textAlign(TextAlign.Center) 542 }, (item: number) => item.toString()) 543 } 544 } 545} 546``` 547 548正例代码如下: 549 550```typescript 551struct StackExample2 { 552 @State isVisible: boolean = true; 553 private data: number[] = []; 554 555 aboutToAppear() { 556 for (let i: number = 0; i < Constants.IMAGE_TOTAL_NUM; i++) { 557 this.data.push(i); 558 } 559 } 560 561 build() { 562 Column() { // 父容器 563 Button('Switch Hidden and Show').onClick(() => { 564 this.isVisible = !this.isVisible; 565 }) 566 567 Stack() { 568 if (this.isVisible) { 569 Text('New Page').width(100).height(30).backgroundColor(0xd2cab3) 570 } 571 }.width(100).height(30) // 在指定宽高的Stack容器内,内部的Text组件变化只会在容器内部做布局和测量更新,不会影响到容器外ForEach中的Text组件。 572 573 ForEach(this.data, (item: number) => { // Stack容器指定了宽高,不会影响到这一层兄弟节点 574 Text(`Item value: ${item}`) 575 .fontSize($r('app.integer.font_size_20')) 576 .width($r('app.string.layout_100_percent')) 577 .textAlign(TextAlign.Center) 578 }, (item: number) => item.toString()) 579 } 580 } 581``` 582#### 减少不必要的参数层次传递 583 584@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink三种方案的实现方式是逐级向下传递状态,当共享状态的组件间层级相差较大时,会出现状态层层传递的现象。对于没有使用该状态的中间组件而言,这是“额外的消耗”。因此,对于跨越多层的状态变量传递,使用@Provide+@Consume方案更为合理。 585 586反例代码如下: 587 588```typescript 589// 父组件 590@Component 591struct componentParent{ 592 @State data: Data = {}; 593 594 aboutToAppear() { 595 // 获取子组件数据 596 this.data = getData(); 597 } 598 599 build() { 600 Column() { 601 componentSon({ data: this.data }) 602 } 603 } 604} 605 606// 子组件 607@Component 608struct componentSon{ 609 // 获取传递参数 610 @Prop data: Data; 611 612 build() { 613 Column() { 614 Text(data.text) 615 componentGrandSon({ data: this.data }) 616 } 617 } 618} 619 620@Component 621struct componentGrandSon{ 622 // 获取传递参数 623 @Prop data: Data; 624 625 build() { 626 Column() { 627 Text(data.text) 628 } 629 } 630} 631``` 632 633正例代码如下: 634 635```typescript 636// 父组件 637@Component 638struct componentParent{ 639 @Provide('data') data: Data = {}; 640 641 aboutToAppear() { 642 // 获取子组件数据 643 this.data = getData() 644 } 645 646 build() { 647 Column() { 648 componentSon({ data: this.data }) 649 } 650 } 651} 652 653// 子组件 654@Component 655struct componentSon{ 656 // 获取传递参数 657 @Consume("data") data: Data; 658 659 build() { 660 Column() { 661 Text(data.text) 662 componentGrandSon({ data: this.data }) 663 } 664 } 665} 666 667@Component 668struct componentGrandSon{ 669 // 获取传递参数 670 @Consume("data") data: Data; 671 672 build() { 673 Column() { 674 Text(data.text) 675 } 676 } 677} 678``` 679 680#### 避免滥用@Provide+@Consume 681 682在父子组件关联的场景下,@Provide+@Consume开销要大于@State+@Prop/@Link,因此在该场景下推荐使用@State+@Prop/@Link的组合。 683 684反例代码如下: 685 686```typescript 687// 父组件 688@Component 689struct componentParent{ 690 @Provide("data") data: Data = {}; 691 692 aboutToAppear() { 693 // 获取子组件数据 694 this.data = getData(); 695 } 696 697 build() { 698 Column() { 699 componentSon() 700 } 701 } 702} 703 704// 子组件 705@Component 706struct componentSon{ 707 // 获取传递参数 708 @Consume("data") data: Data; 709 710 build() { 711 Column() { 712 Text(data.text) 713 } 714 } 715} 716``` 717 718正例代码如下: 719 720```typescript 721// 父组件 722@Component 723struct componentParent{ 724 @State data:Data = {}; 725 726 aboutToAppear() { 727 // 获取子组件数据 728 this.data = getData(); 729 } 730 731 build() { 732 Column() { 733 componentSon({ data: this.data }) 734 } 735 } 736} 737 738// 子组件 739@Component 740struct componentSon{ 741 // 获取传递参数 742 @Prop data:Data; 743 744 build() { 745 Column() { 746 Text(data.text) 747 } 748 } 749} 750``` 751 752### 精准控制状态变量关联组件数量 753 754应该控制状态变量关联的组件数量,如果一个状态关联过多的组件,当这个变量更新时会引起过多的组件重新绘制渲染,建议关联数量限制在20个以内,达到[精准控制组件的更新范围](precisely-control-render-scope.md)。 755 756#### 控制状态变量关联组件数量 757 758反例代码如下: 759 760```typescript 761@Observed 762class Translate { 763 translateX: number = 20; 764} 765@Component 766struct Title { 767 @ObjectLink translateObj: Translate; 768 build() { 769 Row() { 770 Image($r('app.media.icon')) 771 .translate({ 772 x: this.translateObj.translateX // this.translateObj.translateX used in two component both in Row 773 }) 774 Text("Title") 775 .translate({ 776 x: this.translateObj.translateX 777 }) 778 } 779 } 780} 781@Entry 782@Component 783struct Page { 784 @State translateObj: Translate = new Translate(); 785 build() { 786 Column() { 787 Title({ 788 translateObj: this.translateObj 789 }) 790 Stack() { 791 } 792 .translate({ 793 x:this.translateObj.translateX // this.translateObj.translateX used in two components both in Column 794 }) 795 Button("move") 796 .translate({ 797 x: this.translateObj.translateX 798 }) 799 .onClick(() => { 800 animateTo({ 801 duration: 50 802 }, () => { 803 this.translateObj.translateX = (this.translateObj.translateX + 50) % 150 804 }) 805 }) 806 } 807 } 808} 809``` 810 811正例代码如下: 812 813```typescript 814@Observed 815class Translate { 816 translateX: number = 20; 817} 818@Component 819struct Title { 820 build() { 821 Row() { 822 Image($r('app.media.icon')) 823 Text("Title") 824 } 825 } 826} 827@Entry 828@Component 829struct Page1 { 830 @State translateObj: Translate = new Translate(); 831 build() { 832 Column() { 833 Title() 834 Stack() { 835 } 836 Button("move") 837 .onClick(() => { 838 animateTo({ 839 duration: 50 840 }, () => { 841 this.translateObj.translateX = (this.translateObj.translateX + 50) % 150 842 }) 843 }) 844 } 845 .translate({ // the component in Column shares the same property translate 846 x: this.translateObj.translateX 847 }) 848 } 849} 850``` 851 852#### 控制对象级状态变量成员数量 853 854应该控制对象级状态变量的成员变量关联的组件数量。开发者封装一个数据结构类用于进行状态变量关联时,应该避免过多的成员变量关联大量ArkUI组件,这种情况下,当这个大对象的一个成员变量更新时,会导致所有关联这个大对象的组件都同时进行刷新,造成不必要的性能损耗,从而影响帧率。 855 856反例代码如下: 857 858```typescript 859@Observed 860class AnimationParams { 861 translateX: number = 0; 862 translateY: number = 0; 863 alpha: number = 1; 864 rotationX: number = 0; 865 rotationY: number = 0; 866 centerX: number = 0; 867 centerY: number = 0; 868 angle: number = 0; 869 scaleX: number = 1; 870 scaleY: number = 1; 871} 872 873@Entry 874@Component 875struct Page { 876 @State animationParam: AnimationParams = new AnimationParams(); 877 878 build() { 879 Column() { 880 Row() { 881 Image($r('app.media.startIcon')) 882 .translate({ 883 x: this.animationParam.translateX, 884 y: this.animationParam.translateY 885 }) 886 .rotate({ 887 x: this.animationParam.rotationX, 888 y: this.animationParam.translateY, 889 centerX: this.animationParam.centerX, 890 centerY: this.animationParam.centerY, 891 angle: this.animationParam.angle 892 }) 893 .opacity(this.animationParam.alpha) 894 .scale({ 895 x: this.animationParam.scaleX, 896 y: this.animationParam.scaleY, 897 centerX: this.animationParam.centerX, 898 centerY: this.animationParam.centerY 899 }) 900 .animation({ 901 duration: 3000 902 }) 903 } 904 905 Button('点击播放动画') 906 .onClick(() => { 907 this.animationParam.translateX = 300; 908 this.animationParam.translateY = 200; 909 this.animationParam.rotationX = 90; 910 this.animationParam.rotationY = 90; 911 this.animationParam.centerX = 20; 912 this.animationParam.centerY = 20; 913 this.animationParam.angle = 270; 914 this.animationParam.alpha = 0.5; 915 this.animationParam.scaleX = 3; 916 this.animationParam.scaleY = 3; 917 }) 918 } 919 } 920} 921``` 922 923正例代码如下: 924 925```typescript 926@Observed 927class RotationAnimationParams { 928 rotationX: number = 0; 929 rotationY: number = 0; 930 centerX: number = 0; 931 centerY: number = 0; 932 angle: number = 0; 933} 934 935@Observed 936class TranslateAnimationParams { 937 translateX: number = 0; 938 translateY: number = 0; 939} 940 941@Observed 942class AlphaAnimationParams { 943 alpha: number = 1; 944} 945 946@Observed 947class ScaleAnimationParams { 948 scaleX: number = 1; 949 scaleY: number = 1; 950 centerX: number = 0; 951 centerY: number = 0; 952} 953 954@Entry 955@Component 956struct Page { 957 @State rotationAnimation: RotationAnimationParams = new RotationAnimationParams(); 958 @State translateAnimation: TranslateAnimationParams = new TranslateAnimationParams(); 959 @State alphaAnimation: AlphaAnimationParams = new AlphaAnimationParams(); 960 @State scaleAnimation: ScaleAnimationParams = new ScaleAnimationParams(); 961 962 build() { 963 Column() { 964 Row() { 965 Image($r('app.media.startIcon')) 966 .translate({ 967 x: this.translateAnimation.translateX, 968 y: this.translateAnimation.translateY 969 }) 970 .rotate({ 971 x: this.rotationAnimation.rotationX, 972 y: this.rotationAnimation.rotationY, 973 centerX: this.rotationAnimation.centerX, 974 centerY: this.rotationAnimation.centerY, 975 angle: this.rotationAnimation.angle 976 }) 977 .opacity(this.alphaAnimation.alpha) 978 .scale({ 979 x: this.scaleAnimation.scaleX, 980 y: this.scaleAnimation.scaleY, 981 centerX: this.scaleAnimation.centerX, 982 centerY: this.scaleAnimation.centerY 983 }) 984 .animation({ 985 duration: 3000 986 }) 987 } 988 989 Button('点击播放动画') 990 .onClick(() => { 991 this.rotationAnimation.rotationX = 90; 992 this.rotationAnimation.rotationY = 90; 993 this.rotationAnimation.centerX = 20; 994 this.rotationAnimation.centerY = 20; 995 this.rotationAnimation.angle = 270; 996 997 this.translateAnimation.translateX = 300; 998 this.translateAnimation.translateY = 200; 999 1000 this.alphaAnimation.alpha = 0.5; 1001 1002 this.scaleAnimation.scaleX = 3; 1003 this.scaleAnimation.scaleY = 3; 1004 this.scaleAnimation.centerX = 20; 1005 this.scaleAnimation.centerY = 20; 1006 }) 1007 } 1008 } 1009} 1010``` 1011 1012### 避免不必要的创建和读取状态变量 1013 1014避免不必要的创建和读取状态变量,减少性能损耗。 1015 1016#### 删除冗余的状态变量标记 1017 1018状态变量的管理有一定的开销,应在合理场景使用,普通的变量用状态变量标记可能会导致性能劣化。 1019 1020反例代码如下: 1021 1022```typescript 1023@Observed 1024class Translate { 1025 translateX: number = 20; 1026} 1027 1028@Entry 1029@Component 1030struct UnnecessaryState1 { 1031 @State translateObj: Translate = new Translate(); // 变量translateObj没有关联任何UI组件,不应该定义为状态变量 1032 @State buttonMsg: string = 'I am button'; // 变量buttonMsg没有关联任何UI组件,不应该定义为状态变量 1033 1034 build() { 1035 } 1036} 1037``` 1038以上示例中变量translateObj、buttonMsg没有关联任何UI组件,不应该定义为状态变量,否则读写状态变量都会影响性能。 1039 1040```typescript 1041@Observed 1042class Translate { 1043 translateX: number = 20; 1044} 1045 1046@Entry 1047@Component 1048struct UnnecessaryState2 { 1049 @State buttonMsg: string = 'I am button'; 1050 1051 build() { 1052 Column() { 1053 Button(this.buttonMsg) // 这里只是读取变量buttonMsg的值,没有任何写的操作 1054 } 1055 } 1056} 1057``` 1058以上示例中变量buttonMsg仅有读取操作,没有修改过,没有修改过的状态变量不应该定义为状态变量,否则读状态变量会影响性能。 1059 1060正例代码如下: 1061 1062```typescript 1063@Observed 1064class Translate { 1065 translateX: number = 20; 1066} 1067 1068@Entry 1069@Component 1070struct NecessaryState { 1071 @State translateObj: Translate = new Translate(); // 同时存在读写操作,并关联了Button组件,推荐使用状态变量 1072 buttonMsg: string = 'I am button'; // 仅读取变量buttonMsg的值,没有任何写的操作,直接使用一般变量即可 1073 1074 build() { 1075 Column() { 1076 Button(this.buttonMsg) 1077 .onClick(() => { 1078 animateTo( 1079 { 1080 duration: 50 1081 }, () => { 1082 this.translateObj.translateX = (this.translateObj.translateX + 50) % 150; // 点击时给变量translateObj重新赋值 1083 }) 1084 }) 1085 }.translate({ 1086 x:this.translateObj.translateX // 读取translateObj中的值 1087 }) 1088 } 1089} 1090``` 1091没有关联任何UI组件的状态变量和没有修改过的状态变量不应该定义为状态变量,直接使用一般变量即可,否则会影响性能。 1092 1093#### 避免在For/while等循环函数中重复读取状态变量 1094 1095状态变量的读取耗时远大于普通变量的读取耗时,因此要避免重复读取状态变量,而是应该放在循环外面读取,例如在打印For/while循环中打印状态变量的日志信息。 1096 1097反例代码: 1098 1099```typescript 1100import hiTraceMeter from '@ohos.hiTraceMeter'; 1101 1102@Entry 1103@Component 1104struct Page { 1105 @State message: string = ''; 1106 1107 build() { 1108 Column() { 1109 Button('点击打印日志') 1110 .onClick(() => { 1111 hiTraceMeter.startTrace('print', 1); 1112 for (let i = 0; i < 10; i++) { 1113 console.info(this.message); 1114 } 1115 hiTraceMeter.finishTrace('print', 1); 1116 }) 1117 } 1118 } 1119} 1120``` 1121抓取Trace图如下: 1122 1123 1124正例代码: 1125 1126```typescript 1127import hiTraceMeter from '@ohos.hiTraceMeter'; 1128 1129@Entry 1130@Component 1131struct Page { 1132 @State message: string = ''; 1133 1134 build() { 1135 Column() { 1136 Button('点击打印日志') 1137 .onClick(() => { 1138 hiTraceMeter.startTrace('print', 1); 1139 let logMessage: string = this.message; 1140 for (let i = 0; i < 10; i++) { 1141 console.info(logMessage); 1142 } 1143 hiTraceMeter.finishTrace('print', 1); 1144 }) 1145 } 1146 } 1147} 1148``` 1149抓取Trace图如下: 1150 1151 1152由此可见,使用普通变量代替状态变量在For/while循环中读取,可以减少耗时,因此在For/while循环中频繁读取变量时,可使用普通变量代替状态变量。 1153## 第四要素:合理使用系统接口,避免冗余操作 1154 1155应该合理使用系统的高频回调接口,删除不必要的Trace和日志打印,避免冗余操作,减少系统开销,[避免开发过程中的冗余操作](avoiding-redundant-operations.md)。 1156 1157### 避免在系统高频回调用进行冗余和耗时操作 1158 1159应该避免在onDidScroll、onAreaChange等系统高频的回调接口中进行冗余和耗时操作,这些接口在系统的每一帧绘制中都会执行回调操作,因此在这些接口中进行冗余和耗时操作会大量消耗系统资源,影响应用运行性能。 1160 1161#### 避免在系统高频回调用打印Trace 1162 1163Trace的打印是会额外消耗系统性能的,因此应该避免在这些系统高频回调接口中打印Trace,示例代码如下: 1164 1165```typescript 1166// 反例 1167import { hiTraceMeter } from '@kit.PerformanceAnalysisKit'; 1168 1169@Component 1170struct NegativeOfOnDidScroll { 1171 private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 1172 1173 build() { 1174 Scroll() { 1175 ForEach(this.arr, (item: number) => { 1176 Text("ListItem" + item) 1177 .width("100%") 1178 .height("100%") 1179 }, (item: number) => item.toString()) 1180 } 1181 .width('100%') 1182 .height('100%') 1183 .onDidScroll(() => { 1184 hiTraceMeter.startTrace("ScrollSlide", 1002); 1185 // 业务逻辑 1186 // ... 1187 hiTraceMeter.finishTrace("ScrollSlide", 1002); 1188 }) 1189 } 1190} 1191 1192// 正例 1193@Component 1194struct PositiveOfOnDidScroll { 1195 private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 1196 1197 build() { 1198 Scroll() { 1199 List() { 1200 ForEach(this.arr, (item: number) => { 1201 ListItem() { 1202 Text("TextItem" + item) 1203 } 1204 .width("100%") 1205 .height(100) 1206 }, (item: number) => item.toString()) 1207 } 1208 .divider({ strokeWidth: 3, color: Color.Gray }) 1209 } 1210 .width('100%') 1211 .height('100%') 1212 .onDidScroll(() => { 1213 // 业务逻辑 1214 // ... 1215 }) 1216 } 1217} 1218``` 1219 1220#### 避免在系统高频回调用打印日志 1221 1222日志的打印是会额外消耗系统性能的,特别是有些日志还读取了状态变量的信息,会加剧资源开销,因此应该避免在这些系统高频回调接口中打印日志,示例代码如下: 1223 1224```typescript 1225// 反例 1226import { hilog } from '@kit.PerformanceAnalysisKit'; 1227 1228@Component 1229struct NegativeOfOnDidScroll { 1230 private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 1231 1232 build() { 1233 Scroll() { 1234 List() { 1235 ForEach(this.arr, (item: number) => { 1236 ListItem() { 1237 Text("TextItem" + item) 1238 } 1239 .width("100%") 1240 .height(100) 1241 }, (item: number) => item.toString()) 1242 } 1243 .divider({ strokeWidth: 3, color: Color.Gray }) 1244 } 1245 .width('100%') 1246 .height('100%') 1247 .onDidScroll(() => { 1248 hilog.info(1002, 'Scroll', 'TextItem'); 1249 // 业务逻辑 1250 // ... 1251 }) 1252 } 1253} 1254 1255// 正例 1256@Component 1257struct PositiveOfOnDidScroll { 1258 private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 1259 1260 build() { 1261 Scroll() { 1262 List() { 1263 ForEach(this.arr, (item: number) => { 1264 ListItem() { 1265 Text("TextItem" + item) 1266 } 1267 .width("100%") 1268 .height(100) 1269 }, (item: number) => item.toString()) 1270 } 1271 .divider({ strokeWidth: 3, color: Color.Gray }) 1272 } 1273 .width('100%') 1274 .height('100%') 1275 .onDidScroll(() => { 1276 // 业务逻辑 1277 // ... 1278 }) 1279 } 1280} 1281``` 1282 1283### 删除冗余Trace和日志打印 1284 1285Trace和日志打印会比较消耗系统性能,因此应该避免冗余的Trace和日志打印。推荐在Release版本中,尽量删除所有Trace信息,删除Debug日志,减少额外的系统开销。 1286 1287#### 在Release版本中删除Trace 1288 1289Trace会比较消耗系统性能,建议在Release版本删除Trace打印。 1290 1291反例代码如下: 1292 1293```typescript 1294@Component 1295struct NegativeOfTrace { 1296 aboutToAppear(): void { 1297 hitrace.startTrace("HITRACE_TAG_APP", 1003); 1298 // 业务代码 1299 // ... 1300 hitrace.finishTrace("HITRACE_TAG_APP", 1003); 1301 } 1302 build() { 1303 // 业务代码 1304 // ... 1305 } 1306} 1307``` 1308 1309正例代码如下: 1310 1311```typescript 1312@Component 1313struct PositiveOfTrace { 1314 aboutToAppear(): void { 1315 // 业务代码 1316 // ... 1317 } 1318 build() { 1319 // 业务代码 1320 // ... 1321 } 1322} 1323``` 1324 1325#### 在Release版本中删除Debug日志 1326 1327虽然在Release版本中不会打印debug级别日志,但是如果在日志的入参中进行了参数拼接,字符串拼接的逻辑还会执行,会有冗余开销,因此为了[避免开发过程中的冗余操作](avoiding-redundant-operations.md),建议在Release版本删除Debug日志打印。 1328 1329反例代码如下: 1330 1331```typescript 1332@Component 1333struct NegativeOfDebug { 1334 @State string1: string = 'a'; 1335 @State string2: string = 'b'; 1336 1337 aboutToAppear(): void { 1338 hilog.debug(1004, 'Debug', (this.string1 + this.string2)); 1339 // 业务代码 1340 // ... 1341 } 1342 1343 build() { 1344 // 业务代码 1345 // ... 1346 } 1347} 1348``` 1349 1350正例代码如下: 1351 1352```typescript 1353@Component 1354struct PositiveOfDebug { 1355 aboutToAppear(): void { 1356 // 业务代码 1357 // ... 1358 } 1359 build() { 1360 // 业务代码 1361 // ... 1362 } 1363} 1364``` 1365 1366### 避免设置冗余系统回调监听 1367 1368冗余的系统回调监听,会额外消耗系统开销去做计算和函数回调消耗。比如设置了onAreaChange,就算回调中没有任何逻辑,系统也会在C++侧去计算该组件的大小和位置变化情况,并且把结果回调到TS侧,额外消耗了系统开销。 1369 1370反例代码如下: 1371 1372```typescript 1373@Component 1374struct NegativeOfOnClick { 1375 build() { 1376 Button('Click', { type: ButtonType.Normal, stateEffect: true }) 1377 .onClick(() => { 1378 hitrace.startTrace("ButtonClick", 1004); 1379 hilog.info(1004, 'Click', 'ButtonType.Normal') 1380 hitrace.finishTrace("ButtonClick", 1004); 1381 // 业务代码 1382 // ... 1383 }) 1384 .onAreaChange((oldValue: Area, newValue: Area) => { 1385 // 无任何代码 1386 }) 1387 } 1388} 1389``` 1390 1391正例代码如下: 1392 1393```typescript 1394@Component 1395struct PositiveOfOnClick { 1396 build() { 1397 Button('Click', { type: ButtonType.Normal, stateEffect: true }) 1398 .onClick(() => { 1399 // 业务代码 1400 // ... 1401 }) 1402 } 1403``` 1404 1405## 使用性能工具分析和定位问题 1406 1407学会合理使用工具进行问题分析和定位,提升问题解决效率。 1408 1409### 学会使用IDE的Profier工具定位问题 1410 1411通过使用Profier工具,定位应用开发过程中的各种性能问题,详细的使用方法可以参考文章:[性能分析工具CPU Profiler](application-performance-analysis.md)。 1412 1413### 使用SmartPerf-Host分析应用性能 1414 1415[SmartPerf-Host](performance-optimization-using-smartperf-host.md)是一款深入挖掘数据、细粒度展示数据的性能功耗调优工具,可采集CPU调度、频点、进程线程时间片、堆内存、帧率等数据,采集的数据通过泳道图清晰地呈现给开发者,同时通过GUI以可视化的方式进行分析。工具当前为开发者提供了五个分析模板,分别是帧率分析、CPU/线程调度分析、应用启动分析、TaskPool分析、动效分析。 1416 1417### 使用状态变量组件定位工具分析状态变量关联信息 1418 1419开发者可以使用[状态变量组件定位工具](state_variable_dfx_pratice.md)获取状态管理相关信息,例如自定义组件拥有的状态变量、状态变量的同步对象和关联组件等,了解状态变量影响UI的范围,写出高性能应用代码。 1420 1421### 使用常用trace使用指导协助定位性能问题 1422 1423本文旨在介绍[常用trace使用指导](common-trace-using-instructions.md),解释它们的含义和用途,并阐述如何通过这些Trace来识别潜在的性能问题。同时,还将详细介绍Trace的工作原理,帮助开发者更好地理解这些Trace及如何实现性能数据的采集和分析。通过本文的阅读,开发者将对Trace有一个深入的了解,为应用程序性能优化提供有力支持。