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