HarmonyOS应用开发性能优化(篇二)

打印 上一主题 下一主题

主题 2027|帖子 2027|积分 6081

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
承接上一篇:HarmonyOS应用开发性能优化(篇一)
1. 尽量减少布局的嵌套层数

在进行页面布局开发时,去除冗余的布局嵌套,利用相对布局、绝对定位、自定义布局、Grid、GridRow等扁平化布局,减少布局的嵌套层数,制止体系绘制更多的布局组件,利用@Builder替换自定义组件减少嵌套层级,到达优化性能、减少内存占用的目的。
1.1. 删除无用的Stack/Column/Row嵌套

比方在Row容器包罗一个同样也是Row容器的子级。这种嵌套实际是多余的,而且会给布局条理布局造成不须要的开销。
优化前:
  1. Row() {
  2.   Row() {
  3.     Image()
  4.     Text()
  5.   }
  6.   Image()
  7. }
复制代码
优化后:
  1. Row() {
  2.   Image()
  3.   Text()
  4.   Image()
  5. }
复制代码
1.2. 移除冗余节点

应该删除冗余的布局嵌套,比方非@Entry组件的build最外层的无用容器嵌套、无用的Stack或Column嵌套等,减少布局层数。
优化前:
  1. @Component
  2. struct ComponentA {
  3.   build() {
  4.     Column() {
  5.       ComponentB()
  6.     }
  7.   }
  8. }
  9. @Component
  10. struct ComponentB {
  11.   build() {
  12.     // 应该删除build最外层的无用容器嵌套
  13.     Column() {
  14.       Text("")
  15.     }
  16.   }
  17. }
复制代码
优化后:
  1. @Component
  2. struct ComponentA {
  3.   build() {
  4.     Column() {
  5.       ComponentB()
  6.     }
  7.   }
  8. }
  9. @Component
  10. struct ComponentB {
  11.   build() {
  12.     Text("")
  13.   }
  14. }
复制代码
1.3. 利用体系高阶组件实现布局嵌套

利用体系提供的高阶组件可以更高效地实现复杂布局嵌套:


  • List组件不仅支持线性排布,还具备懒加载与滑动功能,适用于列表型内容展示。
  • Grid/GridItem组件提供网格布局本事,同样支持懒加载与滑动,适合宫格样式内容组织。
  • RelativeContainer为相对布局容器,可通过描述组件间的相对位置关系进行横向与纵向的二维布局控制,适用于复杂排版需求。
这些组件均具备较强的性能优化本事,推荐优先利用。
2. 公道管理状态变量

公道地利用状态变量,精准控制组件的更新范围,控制状态变量关联组件数目,控制对象级状态变量的成员变量关联组件数,高负载场景利用AttributeModifier替代@State,减少体系的组件渲染负载,提升应用流畅度。
3. 公道利用体系接口,制止冗余操作

公道利用体系的高频回调接口,删除不须要的Trace和日志打印,制止注册体系冗余回调,减少体系开销。
3.1. 优化状态管理,精准控制革新范围

3.1.1. 控制对象级状态变量成员关联的组件数目,减小革新范围

滑动场景下,应该控制状态变量关联的组件数目,假如一个状态关联过多的组件,当这个变量更新时会引起过多的组件重新绘制渲染,建议关联数目限制在20个以内。公道控制状态更新范围,制止关联革新较大范围或者渲染较慢的组件。


  • 不推荐
  1. class Info {
  2.   name: string = ""
  3.   userDefine: string = ""
  4.   size: number = 0
  5.   image: Resource | undefined = undefined
  6. }
  7. @Entry
  8. @Component
  9. struct Index {
  10.   @State userInfo: Info = new Info()
  11.   build() {
  12.     Row() {
  13.       Column() {
  14.         // Image和Text等信息放在一个userInfo对象中
  15.         Image(this.userInfo.image)
  16.           .width(50)
  17.           .height(50)
  18.         Text(this.userInfo.userDefine)
  19.           .fontSize(50)
  20.           .fontWeight(FontWeight.Bold)
  21.         Text(this.userInfo.size.toString())
  22.           .fontSize(50)
  23.           .fontWeight(FontWeight.Bold)
  24.         Text(this.userInfo.name)
  25.           .fontSize(50)
  26.           .fontWeight(FontWeight.Bold)
  27.       }
  28.      .width('100%')
  29.     }
  30.    .height('100%')
  31.   }
  32. }
复制代码


  • 推荐
  1. class Info {
  2.   name: string = ""
  3.   userDefine: string = ""
  4.   size: number = 0
  5. }
  6. @Entry
  7. @Component
  8. struct Index {
  9.   @State userInfo: Info = new Info()
  10.   @State image: Resource | undefined = undefined
  11.   build() {
  12.     Row() {
  13.       Column() {
  14.         // Image单独绑定一个状态变量,避免只更新text时也触发Image更新
  15.         Image(this.image)
  16.           .width(50)
  17.           .height(50)
  18.         Text(this.userInfo.userDefine)
  19.           .fontSize(50)
  20.           .fontWeight(FontWeight.Bold)
  21.         Text(this.userInfo.size.toString())
  22.           .fontSize(50)
  23.           .fontWeight(FontWeight.Bold)
  24.         Text(this.userInfo.name)
  25.           .fontSize(50)
  26.           .fontWeight(FontWeight.Bold)
  27.       }
  28.      .width('100%')
  29.     }
  30.    .height('100%')
  31.   }
  32. }
复制代码
3.1.2. 公道拆分对象,制止过度筹划

太过寻求状态布局拆分可能在某些场景导致组件筹划过度,不利于维护。此时,可以将对象或类上经常一起改变的几个属性聚合成一个新的对象或类模型,并利用@Observed装饰器修饰,再作为属性挂载到之前的对象或类上。通过此方法,当属性变革时ArkUI只会关照变革给新的对象或类,不会关照最上层的对象。这样既可以有用的减少无用渲染次数,又能使代码更好维护。
如类ClassA上存在属性b、c、d。其中c和d经常一起发生变革,即当c的状态修改时同时也要修改d的状态。
  1. class ClassA {
  2.   b: string
  3.   c: number
  4.   d: boolean
  5. }
复制代码
此时,将c和d组合在一起做为新的类ClassE的属性并利用@Observed装饰器修饰。对于ClassA去掉c、d属性,新增属性e且其类型为ClassE,筹划如下:
  1. class ClassA {
  2.   b: string
  3.   e: ClassE
  4. }
  5. @Observed
  6. class ClassE {
  7.   c: number
  8.   d: boolean
  9. }
复制代码
当ClassA实例的属性e中的属性c的值变革时,状态变革会关照利用ClassE实例的组件重新渲染,不会关照关联ClassA实例的组件更新,即只利用了ClassA实例b属性的组件不会重新渲染。
3.1.3. 公道利用状态变量,制止冗余消耗

场景:利用一个状态变量flag控制两个组件,其中一个组件需要播放渐变更画,另一个组件不播放动画直接表现或潜伏。


  • 不推荐的实现
  1. @Component
  2. struct Index {
  3.   @State flag: boolean = false
  4.   build() {
  5.     Column({ space: 24 }) {
  6.       Button("changeStatus").onClick((event: ClickEvent) => {
  7.         animateTo({ duration: 600 }, () => {
  8.           this.flag =!this.flag
  9.         })
  10.       })
  11.       Image($r('app.media.startIcon'))
  12.        .width(56)
  13.        .aspectRatio(1)
  14.         // 使用一个flag导致冗余的动画animation设置,
  15.         // 应该设置一个新的状态变量@State flag2单独控制该属性的刷新
  16.        .visibility(this.flag? Visibility.Visible : Visibility.None)
  17.        .animation({ duration: 0 })
  18.       Row().width(100).aspectRatio(1).backgroundColor(this.flag? '#000' : '#ff0')
  19.     }
  20.   }
  21. }
复制代码


  • 推荐的实现
  1. @Component
  2. struct Index {
  3.   @State flag: boolean = false
  4.   @State flag2: boolean = false
  5.   build() {
  6.     Column({ space: 24 }) {
  7.       Button("changeStatus").onClick((event: ClickEvent) => {
  8.         animateTo({ duration: 600 }, () => {
  9.           this.flag =!this.flag
  10.         })
  11.       })
  12.       Image($r('app.media.startIcon'))
  13.        .width(56)
  14.        .aspectRatio(1)
  15.        .visibility(this.flag2? Visibility.Visible : Visibility.None)
  16.       Row().width(100).aspectRatio(1).backgroundColor(this.flag? '#000' : '#ff0')
  17.     }
  18.   }
  19. }
复制代码
3.2. 精准控制组件的更新范围

3.2.1. 减少不须要的参数条理传递

@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink三种方案的实现方式是逐级向下传递状态,当共享状态的组件间层级相差较大时,会出现状态层层传递的现象。对于没有利用该状态的中间组件而言,这是“额外的消耗”。因此,对于跨越多层的状态变量传递,利用@Provide+@Consume方案更为公道。
同时,也要注意不要滥用@Provide+@Consume,应优先选择共享范围本事小的装饰器方案,减少差异模块间的数据耦合,便于状态实时回收。建议选择装饰器的优先级为:@State+@Prop、@State+@Link、@State+@Observed+@ObjectLink > @Provide+@Consume > LocalStorage > AppStorage。
3.2.2. 利用@Link/@ObjectLink替代@Prop

@Prop是深拷贝,@Link/@ObjectLink是引用传递。所以在@Prop和@ObjectLink利用效果相同的场景下,优先利用@ObjectLink的方式加快对象创建速度,减少体系内存开销。
3.2.3. 利用DisplaySync精准控制每一帧的组件革新范围

在某些高负载场景中,可以利用DisplaySync控制每一帧的革新范围,平衡负载,从而减低丢帧率。
  1. aboutToAppear(): void {
  2.   // 创建DisplaySync对象
  3.   this.displaySync = displaySync.create()
  4.   // 初始化期望帧率
  5.   let range: ExpectedFrameRateRange = {
  6.     expected: 120,
  7.     min: 60,
  8.     max: 120
  9.   }
  10.   // 设置期望帧率
  11.   this.displaySync.setExpectedFrameRateRange(range)
  12.   // 设置帧回调监听
  13.   this.displaySync.on("frame", () => {
  14.    // ...
  15.   })
  16.   // 开启监听帧回调
  17.   this.displaySync.start()
  18. // ...
  19. }
复制代码
3.3. 制止不须要的创建和读取状态变量

3.3.1. 删除冗余的状态变量标记

状态变量的管理有一定的开销,应在公道场景利用,普通的变量用状态变量标记可能会导致性能劣化。


  • 反例
  1. @Component
  2. struct component {
  3.   @State bgcolor: string | Color = '#ffffffff'
  4.   @State selectColor: string | Color = '#007DFFF'
  5.   build() {
  6.   }
  7. }
复制代码


  • 正例
  1. @Component
  2. struct component {
  3.   bgcolor: string | Color = '#ffffffff'
  4.   selectColor: string | Color = '#007DFFF'
  5.   build() {
  6.   }
  7. }
复制代码
3.3.2. 制止在For/while等循环函数中重复读取状态变量

状态变量的读取耗时远大于普通变量的读取耗时,因此要制止重复读取状态变量,而是应该放在循环外面读取,比方在打印For/while循环中打印状态变量的日志信息。


  • 反例
  1. @Component
  2. struct Page {
  3.   @State message: string = ""
  4.   build() {
  5.     Column() {
  6.       Button('点击打印日志')
  7.        .onClick(() => {
  8.           for (let i = 0; i < 10; i++) {
  9.             console.debug(this.message)
  10.           }
  11.         })
  12.     }
  13.   }
  14. }
复制代码


  • 正例
  1. @Component
  2. struct Page {
  3.   @State message: string = ""
  4.   build() {
  5.     Column() {
  6.       Button('点击打印日志')
  7.        .onClick(() => {
  8.           let logMessage: string = this.message
  9.           for (let i = 0; i < 10; i++) {
  10.             console.debug(logMessage)
  11.           }
  12.         })
  13.     }
  14.   }
  15. }
复制代码
3.4. 制止在体系高频回调用进行冗余和耗时操作

为了提升体系性能,应制止在高频回调函数中执行冗余或耗时操作。比方:onTouch、onItemDragMove、onDragMove、onScroll、onMouse、onVisibleAreaChange、onAreaChange、onActionUpdate、animator的onFrame回调,以及组件复用和生命周期相关的aboutToReuse、aboutToAppear、aboutToDisappear等。这些回调通常在每一帧渲染时都会被触发,若处理不妥,会明显增长体系负载,导致卡顿或掉帧,影响应用整体流畅性。


  • 示例一
  1. // 反例
  2. Scroll() {
  3.   ForEach(this.arr, (item: number) => {
  4.     Text("ListItem" + item)
  5.   }, (item: number) => item.toString())
  6. }
  7. .width("100%")
  8. .height("100%")
  9. .onScroll(() => {
  10.   hiTraceMeter.startTrace('ScrollSlide', 1002)
  11.   // 业务逻辑
  12.   //...
  13.   hiTraceMeter.finishTrace('ScrollSlide', 1002)
  14. })
  15. // 正例
  16. Scroll() {
  17.   ForEach(this.arr, (item: number) => {
  18.     Text("ListItem" + item)
  19.   }, (item: number) => item.toString())
  20. }
  21. .width("100%")
  22. .height("100%")
  23. .onScroll(() => {
  24.   // 业务逻辑
  25.   //...
  26. })
复制代码


  • 示例二
  1. // 反例
  2. Scroll() {
  3.   ForEach(this.arr, (item: number) => {
  4.     Text("ListItem" + item)
  5.   }, (item: number) => item.toString())
  6. }
  7. .width("100%")
  8. .height("100%")
  9. .onScroll(() => {
  10.   hilog.info(1002, 'Scroll', 'ListItem')
  11.   // 业务逻辑
  12.   //...
  13. })
  14. // 正例
  15. Scroll() {
  16.   ForEach(this.arr, (item: number) => {
  17.     Text("ListItem" + item)
  18.   }, (item: number) => item.toString())
  19. }
  20. .width("100%")
  21. .height("100%")
  22. .onScroll(() => {
  23.   // 业务逻辑
  24.   //...
  25. })
复制代码
3.5. 删除冗余Trace和日志打印

Trace和日志打印会比力消耗体系性能,因此我们应该制止冗余的Trace和日志打印。推荐在Release版本中,尽量删除所有Trace信息,删除Debug日志,减少额外的体系开销。在Release版本中,debug日志的入参字符串的拼接也会有开销,特别是拼接状态变量的情况下。


  • 删除冗余trace
  1. //反例代码如下:
  2. @Component
  3. struct ComponentA {
  4.   build() {
  5.   }
  6.   aboutToAppear(): void {
  7.     hiTraceMeter.startTrace('HITRACE_TAG_APP', 1001)
  8.     // 业务代码
  9.     //...
  10.     hiTraceMeter.finishTrace('HITRACE_TAG_APP', 1001)
  11.   }
  12. }
  13. // 正例代码如下:
  14. @Component
  15. struct ComponentB {
  16.   build() {
  17.   }
  18.   aboutToAppear(): void {
  19.     // 业务代码
  20.     //...
  21.   }
  22. }
复制代码


  • Release版本删除debug日志
  1. //反例代码如下:
  2. @Component
  3. struct MyComponentA {
  4.   @State text: string = ""
  5.   build() {
  6.   }
  7.   aboutToAppear(): void {
  8.     hilog.debug(0xFF00, 'textTug', 'ComponentA_aboutToAppear_start' + this.text)
  9.     // 业务代码
  10.     //...
  11.     hilog.debug(0xFF00, 'textTug', 'ComponentA_aboutToAppear_end' + this.text)
  12.   }
  13. }
  14. // 正例代码如下:
  15. @Component
  16. struct MyComponentB {
  17.   @State text: string = ""
  18.   build() {
  19.   }
  20.   aboutToAppear(): void {
  21.     // 业务代码
  22.     //...
  23.   }
  24. }
复制代码
3.6. 制止设置冗余体系回调监听

冗余的体系回调监听,会额外消耗体系开销去做计算和函数回调消耗。好比设置了onAreaChange,就算回调中没有任何逻辑,体系也会在C++侧去计算该组件的巨细和位置变革情况,而且把结果回调到TS侧,额外消耗了体系开销。


  • 反例
  1. //反例代码如下:
  2. @Component
  3. struct NegativeOfOnClick {
  4.   build() {
  5.     Button('Click', { type: ButtonType.Normal, stateEffect: true })
  6.      .onClick(() => {
  7.         hiTraceMeter.startTrace('ButtonClick', 1004)
  8.         hilog.info(1004, 'Click', 'ButtonType.Normal')
  9.         hiTraceMeter.finishTrace('ButtonClick', 1004)
  10.         // 业务代码
  11.         //...
  12.       })
  13.      .onAreaChange((oldValue: Area, newValue: Area) => {
  14.         // 无任何代码
  15.       })
  16.   }
  17. }
复制代码


  • 正例
  1. //正例代码如下:
  2. @Component
  3. struct PositiveOfOnClick {
  4.   build() {
  5.     Button('Click', { type: ButtonType.Normal, stateEffect: true })
  6.      .onClick(() => {
  7.         // 业务代码
  8.         //...
  9.       })
  10.   }
  11. }
复制代码
3.7. 制止在ResourceManager的getXXXSync接口入参中直接利用资源信息

制止在ResourceManager的getXXXSync接口入参中直接利用资源信息,推荐利用资源id作为入参,比方推荐用法为:resourceManager.getStringSync($r('app.string.test').id)


  • 反例
  1. // 反例
  2. aboutToAppear(): void {
  3.   hiTraceMeter.startTrace('getStringSync', 1)
  4.   // getStringSync接口的入参直接使用资源,未使用资源ID
  5.   getContext().resourceManager.getStringSync($r('app.string.test'))
  6.   hiTraceMeter.finishTrace('getStringSync', 1)
  7. }
复制代码


  • 正例
  1. // 正例:
  2. aboutToAppear(): void {
  3.   hiTraceMeter.startTrace('getStringSyncAfter', 2)
  4.   // getStringSync接口的入参使用了资源ID
  5.   getContext().resourceManager.getStringSync($r('app.string.test').id)
  6.   hiTraceMeter.finishTrace('getStringSyncAfter', 2)
  7. }
复制代码
HarmonyOS应用开发性能优化(篇一)
HarmonyOS应用开发性能优化(篇二)
HarmonyOS应用开发性能优化(篇三)
更多内容请参见视频教程:《HarmonyOS应用开发实战指南》

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

没腿的鸟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表