往期推文全新看点
- 鸿蒙(HarmonyOS)北向开发知识点记录~
- 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
- 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
- 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
- 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
- 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
- 记录一场鸿蒙开发岗位面试经历~
- 持续更新中……
简介
在应用开发实践中,有效避免主线程执行冗余与易耗时操作是至关重要的策略。此举能有效降低主线程负载,提升UI的响应速度。面对高频回调接口在短时间内密集触发的场景,需要避免接口内的耗时操作,尽量保证主线程不被长时间占用,从而防止阻塞UI渲染,引发界面卡顿或掉帧现象。本文介绍开发过程中常见的冗余操作,常见的高频回调场景以及其他主线程优化思路。
常见冗余操作
在软件开发中,冗余操作指的是那些不必要、重复执行且对程序功能无实质性贡献的操作。这些操作不仅会浪费计算资源,还可能降低程序的运行效率,特别是在高频调用的场景下,其负面影响更为显著。下面列举一些release版本中常见的冗余操作:
- debug日志打印
- Trace打点
- 冗余空回调
【反例】:release版本中冗余日志打印,Trace打点,以及无业务代码的空回调
import { hiTraceMeter } from '@kit.PerformanceAnalysisKit';
// 冗余操作反例
@Entry
@Component
struct RedundantOperation {
private arr: number[] = [];
aboutToAppear(): void {
for (let i = 0; i < 500; i++) {
this.arr[i] = i;
}
}
build() {
Scroll() {
List() {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('TextItem' + item)
}
.onAreaChange((oldValue, newValue) => {
// 无任何业务操作
})
.width('100%')
.height(100)
}, (item: number) => item.toString())
}
.divider({ strokeWidth: 3, color: Color.Gray })
}
.width('100%')
.height('100%')
.onWillScroll(() => {
hiTraceMeter.startTrace('ScrollSlide', 1001);
console.debug('Debug', ('内容:' + '日志'));
// 业务逻辑
// ...
hiTraceMeter.finishTrace('ScrollSlide', 1001);
})
}
}
【正例】:release版本中删除冗余的debug日志,Trace打点以及无业务代码的空回调
// 冗余操作正例
@Entry
@Component
struct NoRedundantOperation {
private arr: number[] = [];
aboutToAppear(): void {
for (let i = 0; i < 500; i++) {
this.arr[i] =i;
}
}
build() {
Scroll() {
List() {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('TextItem' + item)
}
.width('100%')
.height(100)
}, (item: number) => item.toString())
}
.divider({ strokeWidth: 3, color: Color.Gray })
}
.width('100%')
.height('100%')
.onWillScroll(() => {
// 业务逻辑
// ...
})
}
}
图1反例标签"H:Scrolide"Trace图
通过上图可知,在3.5s的滑动过程中,总计触发了424次日志打印以及Trace追踪,打印一次日志的平均耗时为84μs,由此可以计算出冗余的debug日志浪费了35.616ms。在开发流程中,debug日志打印和Trace追踪无疑是重要的辅助工具,帮助开发者诊断问题和调试代码。然而当完成debug调试阶段后,进入release阶段,保留Trace可能会引入不必要的性能开销,比如占用额外的CPU资源、内存以及存储空间。在release阶段,即使debug日志并未实际打印,但内部的构造逻辑依旧会被执行,这无疑也会造成一定的性能开销。因此当应用进入到release版本,应当清除代码中冗余的Trace追踪以及debug日志。
对于回调函数体内不包含任何业务逻辑代码的冗余回调而言,即使开发者在回调函数内部未进行任何实质性的操作,只要注册了回调接口,如onAreaChange,系统底层仍会耗费资源去监测对应事件的发生,例如计算组件的位置或大小变化,并将这些数据传递给ArkTS侧。即使这些数据最终在ArkTS层没有被有效利用,底层的计算和通信开销已然存在。所以,为了避免不必要的资源消耗,提升应用性能,应当仔细审查并移除这类无实际用途的回调函数注册。开发过程中,除了需要避免冗余操作,还需要注意避免在高频回调场景执行耗时操作,接下来介绍一下高频回调场景以及需要避免的耗时操作。
高频回调场景
高频回调接口通常是指在应用程序运行过程中会被频繁触发的事件或回调函数,以下常见高频回调场景中需要避免执行耗时操作:
- 高频事件回调
- 组件复用回调
- 组件生命周期回调
- 循环渲染
- 组件属性
高频事件回调
例如,触摸事件、拖拽事件、移动事件、组件区域变化事件、滑动事件等系统事件在应用程序运行过程中会被频繁触发,如果在这些回调接口中执行耗时操作,将导致引用出现卡顿丢帧的问题。下方是基于Scroll组件滑动时会高频调用onWillScroll的场景,分析性能差异。
场景案例
【案例一】在onWillScroll回调中执行耗时操作
// onWillScroll高频回调场景反例
@Entry
@Component
struct NegativeOfOnScroll {
private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
count(): number {
let temp: number = 0;
for (let i = 0; i < 1000000; i++) {
temp += 1;
}
return temp;
}
build() {
Scroll() {
List() {
ForEach(this.arr, (item: number) => {
ListItem() {
Text('TextItem' + item)
}
.width('100%')
.height(100)
}, (item: number) => item.toString())
}
.divider({ strokeWidth: 3, color: Color.Gray })
}
.width('100%')
.height('100%')
.onWillScroll(() => {
hiTraceMeter.startTrace(