
#我的鸿蒙开发手记#HarmonyOS应用开发性能优化(篇三) 原创
1. 使用性能调优工具
- ArkUIInspector:用于检查和调试应用程序页面布局的情况
- Launch Insight: 录制和还原从启动应用,到显示首帧过程中的CPU、内存等资源使用情况,用于分析启动耗时长的问题。
- Frame Insight:录制卡顿过程中的关键数据,标注出应用侧、RenderService侧卡顿帧, 用于分析应用卡顿、丢帧的问题。
- Time Insight:通过周期性采集调用栈,识别CPU 耗时高的热点代码段,用于分析卡顿、CPU 占用高、运行速度慢等问题。
- Allocation Insight:录制和分析内存分配记录,用于分析内存峰值高,内存泄漏,内存不足导致应用被强杀等问题。
- Snapshot Insight:录制和分析应用程序中ArkTS对象的分布,通过快照方式对比ArkTS对象分布区别,用于分析内存泄漏问题。
- CPU Insight:录制CPU 调度事件、线程运行状态、CPU 核频率、Trace 等数据,可用于分析卡顿、运行速度慢、应用无响应等问题。
- Smart Perf_Host:开源性能调优平台,支持对CPU调度、频点、进程线程时间片、堆内存、帧率等数据进行采集和展示,展示方式为泳道图
- HiDumper命令行:HiDumper是系统为开发、测试人员、IDE工具提供的系统信息获取工具,帮助开发者分析、定位问题。
- 状态变量组件定位工具:开发者可以使用状态变量组件定位工具获取状态管理相关信息,例如自定义组件拥有的状态变量、状态变量的同步对象和关联组件等,了解状态变量影响UI的范围,写出高性能应用代码。
2. 其他性能调优方法
2.1. 使用Web的同层渲染提升性能
在使用Web组件加载H5页面时,经常会有长列表、视频等场景。由于Web目前最高只有60帧,想要更加流畅的体验,必须要将原生组件放到Web上。
同层渲染和非同层渲染显示Web页面的方式如下:
- Web页面开发者在页面中用Embed标签来表示使用原生系统组件。
- Embed标签创建独立Surface绘制环境,将此Surface返回给应用侧。
- 开发者收到Surface后,将Surface关联BuilderNode节点(原生button,原生视频等),BuilderNode子树渲染结果将绘制到Embed标签上。
2.2. 使用动态加载延后模块加载,提升启动速度
在有些主页场景,会跳转到各个复杂模块中去,有些模块并不是常用模块,我们可以使用动态延迟加载的方式,减少无用模块的加载。页面在引入依赖的加载模块时,如果不是首帧必须显示的内容模块,可以使用动态加载的方式(await import),延迟模块的加载时间,加快首帧显示的加载速度。
- 优化前
import { pageOne, pagesOneData } from './pageOne'
import { pageTwo, pagesTwoData } from './pageTwo'
import { pageThree, pagesThreeData } from './pageThree'
import { pageFour, pagesFourData } from './pageFour'
// ...
import router from '@ohos.router'
- 优化后:
async loadPageOne(key: string) {
if (key == "pageOne") {
let { PageOneLoader } = await import("../pages/PageLoader")
this.PageOneLoader = PageOneLoader
}
}
2.3. 合理使用RenderGroup
首次绘制组件时,若组件被标记为启用renderGroup状态,将对组件以及其子节点进行离屏绘制,将绘制结果进行缓存,此后当需要重新绘制组件时,就会优先使用缓存而不必重新执行绘制了,从而降低绘制负载,优化渲染性能。
build() {
Column() {
Image(this.image)
.height(20)
.width(20)
.objectFit(ImageFit.Contain)
.margin({left: 15})
Text(this.text)
.fontSize(10)
.fontColor("#182431")
.margin({top: 5})
.width(50)
.opacity(0.8)
.textAlign(TextAlign.Center)
}
.backgroundColor("#e3e3e3")
.width(50)
.height(50)
.borderRadius(25)
.renderGroup(true)
}
2.4. 使用显隐控制进行页面缓存
控制元素显示与隐藏是一种常见的场景,使用Visibility.None、if条件判断等都能够实现该效果。其中if条件判断控制的是组件的创建、布局阶段,visibility属性控制的是元素在布局阶段是否参与布局渲染。使用时如果使用的方式不当,将引起性能上的问题。如果会频繁响应显示与隐藏的交互效果,建议使用切换Visibility.None/Visibility.Hidden和Visibility.Visible来控制元素显示与隐藏,在组件无需展示的时候进行缓存,提高性能。
build() {
Column() {
Button("Switch visible and hidden").onClick(() => {
this.isVisible =!(this.isVisible)
}).width('100%')
Stack() {
Scroll() {
Column() {
ForEach(this.data, (item: number) => {
Image($r('app.media.icon')).width('25%').height('12.5%')
}, (item: number) => item.toString())
// 使用显隐控制切换,不会频繁创建与销毁组件
}.visibility(this.isVisible? Visibility.Visible : Visibility.None)
}
}
}
}
2.5. 减少渲染进程的冗余开销
下面通过一个动画场景的示例,演示如何有效减少渲染进程中的冗余开销,从而提升系统性能与界面流畅度。
- 组件转场动画推荐使用transition
组件转场动画指在组件出现和消失时做动画,有两种实现方式:
- 属性动画+动画结束回调逻辑处理,即animateTo (不推荐)。
Text("toggle state")
.onClick(() => {
this.count++
const thisCount: number = this.count
this.show = true
// 通过改变透明度属性,对Text控件做隐藏或出现的动画
animateTo({ duration: 1000, onFinish: () => {
// 最后一个动画且是让Text控件隐藏的动画,再改变条件使控件消失
if(thisCount === this.count && this.mOpacity === 0) {
this.show = false
}
}}, () => {
this.mOpacity = this.mOpacity === 1? 0 : 1
})
})
- 组件转场动画transition (推荐) 。
build() {
Column() {
Row() {
if (this.show) {
Text('value')
// 设置id,使转场可打断
.id("myText")
.transition(TransitionEffect.OPACITY.animation({duration: 1000}))
}
}
.width("100%")
.height(100)
.justifyContent(FlexAlign.Center)
Text("toggle state")
.onClick(() => {
// 通过transition,做透明度的出现或消失动画
this.show =!this.show
})
}
}
- 合理使用动效—动画参数相同时使用同一个animateTo
由于每次animateTo都需要进行动画前后的对比,使用多个animateTo的性能就不如只使用一个animateTo。特别是针对设置在同一个组件的属性,能减少该组件更新的次数。
func1() {
animateTo({curve: Curve.Sharp, duration: 1000}, () => {
// 不推荐
this.w = (this.w === 100? 200 : 100)
})
}
func2() {
animateTo({curve: Curve.Sharp, duration: 1000}, () => {
// 不推荐
this.color = (this.color === Color.Yellow? Color.Red : Color.Yellow)
})
}
func() {
animateTo({curve: Curve.Sharp, duration: 1000}, () => {
// 推荐
this.w = (this.w === 100? 200 : 100)
this.color = (this.color === Color.Yellow? Color.Red : Color.Yellow)
})
}
- 合理使用动效—多次animateTo时统一更新状态变量
animateTo会将执行动画闭包前后的状态进行对比,对差异部分进行动画。为了对比,会在执行animateTo的动画闭包之前,将所有变更的状态变量和脏节点都刷新。如果多个animateTo之间存在状态更新,会导致执行下一个animateTo之前又存在需要更新的脏节点,可能造成冗余更新。
- 不推荐
struct MyComponent {
@State width: number = 200
@State height: number = 50
@State color: Color = Color.Red
build() {
Column() {
Text( "click" )
.height(this.h)
.onClick(() => {
this.w = 100
// h是非动画属性
this.h = 100
animateTo({curve: Curve.Sharp, duration: 1000}, () => {
this.w = 200
})
this.color = Color.Yellow
animateTo({curve: Curve.Linear, duration: 2000}, () => {
this.color = Color.Red
})
})
}
.width("100%")
.height("100%")
}
}
- 推荐
struct MyComponent {
@State w: number = 100
@State h: number = 50
@State color: Color = Color.Yellow
build() {
Column() {
Text("click")
.height(this.h)
.onClick(() => {
this.w = 100
animateTo({curve: Curve.Sharp, duration: 1000}, () => {
this.w = (this.w === 100? 200 : 100)
})
animateTo({curve: Curve.Linear, duration: 2000}, () => {
this.color = (this.color === Color.Yellow? Color.Red : Color.Yellow)
})
})
}
.width("100%")
.height("100%")
}
}
--THE END--
学习更多鸿蒙应用开发技能,请观看视频教程:
