鸿蒙状态变量优化全攻略:告别冗余刷新
📑往期推文全新看点(文中附带最新·鸿蒙全栈学习笔记)
✒️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景?
✒️ 嵌入式开发适不适合做鸿蒙南向开发?看完这篇你就了解了~
✒️ 对于大前端开发来说,转鸿蒙开发究竟是福还是祸?
✒️ 鸿蒙岗位需求突增!移动端、PC端、IoT到底该怎么选?
✒️ 记录一场鸿蒙开发岗位面试经历~
✒️ 持续更新中……
简介
自定义组件中的变量被状态装饰器(@State,@Prop等)装饰后成为状态变量,而状态变量的改变会引起使用该变量的UI组件渲染刷新。状态变量的不合理使用可能会带来冗余刷新等性能问题。开发者可以使用状态变量组件定位工具(hidumper)获取状态管理相关信息,例如自定义组件拥有的状态变量、状态变量的同步对象和关联组件等,了解状态变量影响UI的范围,写出高性能应用代码。
下面通过一个点击按钮更改状态变量引起组件刷新的场景示例,结合hidumper工具,介绍状态变量使用范围不当,导致UI冗余刷新的问题定位。
示例代码
在以下代码中,创建了自定义组件ComponentA、SpecialImage,每个组件都拥有一些状态变量和UI组件。组件ComponentA中存在Move和Scale两个按钮,在按钮的点击回调中改变状态变量的值刷新相应的组件。
// 常量声明
const animationDuration: number = 500; // move动画时长
const opacityChangeValue: number = 0.1; // opacity每次变化的值
const opacityChangeRange: number = 1; // opacity变化的范围
const translateYChangeValue: number = 180; // translateY每次变化的值
const translateYChangeRange: number = 250; // translateY变化的范围
const scaleXChangeValue: number = 0.6; // scaleX每次变化的值
const scaleXChangeRange: number = 0.8; // scaleX每次变化的值
// 样式属性类
class UIStyle {
public translateX: number = 0;
public translateY: number = 0;
public scaleX: number = 0.3;
public scaleY: number = 0.3;
}
@Component
struct ComponentA {
@Link uiStyle: UIStyle; // uiStyle的属性被多个组件使用
build() {
Column() {
// 使用状态变量的组件
SpecialImage({ specialImageUiStyle: this.uiStyle })
Stack() {
Column() {
Image($r('app.media.app_icon'))
.height(78)
.width(78)
.scale({
x: this.uiStyle.scaleX,
y: this.uiStyle.scaleY
})
}
Stack() {
Text('Hello World')
}
}
.translate({
x: this.uiStyle.translateX,
y: this.uiStyle.translateY
})
// 通过按钮点击回调修改状态变量的值,引起相应的组件刷新
Column() {
Button('Move')
.onClick(() => {
animateTo({ duration: animationDuration }, () => {
this.uiStyle.translateY = (this.uiStyle.translateY + translateYChangeValue) % translateYChangeRange;
})
})
Button('Scale')
.onClick(() => {
this.uiStyle.scaleX = (this.uiStyle.scaleX + scaleXChangeValue) % scaleXChangeRange;
})
}
}
}
}
@Component
struct SpecialImage {
@Link specialImageUiStyle: UIStyle;
private opacityNum: number = 0.5; // 默认透明度
private isRenderSpecialImage(): number {
// Image每次渲染时透明度增加0.1, 在0-1之间循环
this.opacityNum = (this.opacityNum + opacityChangeValue) % opacityChangeRange;
return this.opacityNum;
}
build() {
Column() {
Image($r('app.media.app_icon'))
.size({ width: 200, height: 200 })
.scale({
x: this.specialImageUiStyle.scaleX,
y: this.specialImageUiStyle.scaleY
})
.opacity(this.isRenderSpecialImage())
Text("SpecialImage")
}
}
}
@Entry
@Component
struct DFXStateBeforeOptimization {
@State uiStyle: UIStyle = new UIStyle();
build() {
Column() {
ComponentA({
uiStyle: this.uiStyle
})
}
.width('100%')
.height('100%')
}
}
运行上述示例并分别点击按钮,可以看到点击Move按钮和Scale按钮时组件SpecialImage都出现了刷新,运行效果图如下。
图1 修改代码前点击Scale按钮和Move按钮时运行动图

点击Move按钮的时候SpecialImage组件却发生了旋转动画,这就造成了冗余刷新。
问题定位
下面通过这个示例代码结合hidumper工具来介绍冗余刷新的问题定位。
1. 首先在设备上打开应用,进入ComponentA组件所在的页面。
2. 使用以下命令获取示例应用的窗口Id。当前运行的示例应用包名为performancelibrary,可以在输出结果中找到对应窗口名performancelibrary0的WinId,即为应用的窗口Id。或者当应用正处于前台运行时,Focus window的值就是应用的窗口Id。此处示例应用的窗口Id为11,后面的流程中使用的命令都需要指定窗口Id。
hdc shell "hidumper -s WindowManagerService -a '-a'"
**图2 **命令行获取应用窗口Id运行界面

3. 基于上一步获取的窗口Id 11,使用-viewHierarchy命令携带-r 参数递归打印应用的自定义组件树。
hdc shell "hidumper -s WindowManagerService -a '-w 11 -jsdump -viewHierarchy -r'"
打印应用的自定义组件树结果如下:
-----------------ViewPU Hierarchy-----------------
[-viewHierarchy, viewId=4, isRecursive=true]
|--Index[4]
-----------------ViewPU Hierarchy-----------------
[-viewHierarchy, viewId=53, isRecursive=true]
|--DFXStateManagementPage[53]
|--DFXStateManagementHome[55]
-----------------ViewPU Hierarchy-----------------
[-viewHierarchy, viewId=65, isRecursive=true]
|--DFXStateBeforeOptimizationPage[65]
|--DFXStateBeforeOptimization[67]
|--ComponentA[70]
|--SpecialImage[73]
从结果中找到目标组件ComponentA,后面括号中的内容即为组件ComponentA的节点Id 70。
4. 使用命令-stateVariables携带参数-viewId(参数的值为ComponentA的节点Id)获取自定义组件ComponentA中的状态变量信息。
hdc shell "hidumper -s WindowManagerService -a '-w 11 -jsdump -stateVariables -viewId=70'"
打印组件ComponentA的状态变量信息如下:
--------------ViewPU State Variables--------------
[-stateVariables, viewId=70, isRecursive=false]
|--ComponentA[70]
@Link/@Consume (class SynchedPropertyTwoWayPU) 'uiStyle'[71]
|--Owned by @Component 'ComponentA'[70]
|--Sync peers: {
@Link/@Consume (class SynchedPropertyTwoWayPU) 'specialImageUiStyle'[74] <@Component 'SpecialImage'[73]>
}
|--Dependent components: 2 elmtIds: 'Stack[75]', 'Image[77]'
结果显示ComponentA拥有@Link/@Consume类型的状态变量uiStyle。每条状态变量的详细信息都包含状态变量的所属组件、同步对象和关联组件。
5. 以状态变量uiStyle为例。
① Sync peers表示uiStyle在自定义组件SpecialImage中存在@Link/@Consume类型的状态变量specialImageUiStyle订阅数据变化。
② Dependent components表示在ComponentA组件中存在组件Stack[75]和Image[77]使用了状态变量uiStyle,关联组件的数量为2。
所以当uiStyle变化时,影响的组件范围为自定义组件SpecialImage以及系统组件Stack[75]和Image[77]。
图3 ComponentA的状态变量信息

示例中组件SpecialImage仅使用了uiStyle传递到specialImageUiStyle中的属性scaleX、scaleY。但点击Move按钮修改uiStyle中的属性translateY时,引起的uiStyle变化也会导致组件SpecialImage的刷新。所以,可以将uiStyle中的属性scaleX、scaleY提取到状态变量scaleStyle中,属性translateX和translateY提取到状态变量translateStyle中,仅传递scaleStyle给组件SpecialImage,避免不必要的刷新。
由于提取后存在Class的嵌套,因此需要使用@Observed/@ObjectLink装饰器装饰相应的Class和状态变量。修改后的部分代码如下:
// 常量声明
const animationDuration: number = 500; // move动画时长
const opacityChangeValue: number = 0.1; // opacity每次变化的值
const opacityChangeRange: number = 1; // opacity变化的范围
const translateYChangeValue: number = 180; // translateY每次变化的值
const translateYChangeRange: number = 250; // translateY变化的范围
const scaleXChangeValue: number = 0.6; // scaleX每次变化的值
const scaleXChangeRange: number = 0.8; // scaleX每次变化的值
// 样式属性类,嵌套ScaleStyle, TranslateStyle
@Observed
class UIStyle {
translateStyle: TranslateStyle = new TranslateStyle();
scaleStyle: ScaleStyle = new ScaleStyle();
}
// 缩放属性类
@Observed
class ScaleStyle {
public scaleX: number = 0.3;
public scaleY: number = 0.3;
}
// 位移属性类
@Observed
class TranslateStyle {
public translateX: number = 0;
public translateY: number = 0;
}
@Component
struct ComponentA {
@ObjectLink scaleStyle: ScaleStyle;
@ObjectLink translateStyle: TranslateStyle;
build() {
Column() {
SpecialImage({
specialImageScaleStyle: this.scaleStyle
})
// 其他UI组件
Stack() {
Column() {
Image($r('app.media.app_icon'))
.scale({
x: this.scaleStyle.scaleX,
y: this.scaleStyle.scaleY
})
}
Stack() {
Text('Hello World')
}
}
.translate({
x: this.translateStyle.translateX,
y: this.translateStyle.translateY
})
// 通过按钮点击回调修改状态变量的值,引起相应的组件刷新
Column() {
Button('Move')
.onClick(() => {
animateTo({ duration: animationDuration }, () => {
this.translateStyle.translateY =
(this.translateStyle.translateY + translateYChangeValue) % translateYChangeRange;
})
})
Button('Scale')
.onClick(() => {
this.scaleStyle.scaleX = (this.scaleStyle.scaleX + scaleXChangeValue) % scaleXChangeRange;
})
}
}
}
}
@Component
struct SpecialImage {
@Link specialImageScaleStyle: ScaleStyle;
private opacityNum: number = 0.5; // 默认透明度
// isRenderSpecialImage函数
private isRenderSpecialImage(): number {
// Image每次渲染时透明度增加0.1, 在0-1之间循环
this.opacityNum = (this.opacityNum + opacityChangeValue) % opacityChangeRange;
return this.opacityNum;
}
build() {
Column() {
Image($r('app.media.app_icon'))
.scale({
x: this.specialImageScaleStyle.scaleX,
y: this.specialImageScaleStyle.scaleY
})
.opacity(this.isRenderSpecialImage())
Text("SpecialImage")
}
}
}
@Entry
@Component
struct DFXStateAfterOptimization {
@State uiStyle: UIStyle = new UIStyle();
build() {
Stack() {
ComponentA({
scaleStyle: this.uiStyle.scaleStyle,
translateStyle: this.uiStyle.translateStyle,
})
}
}
}
修改后的示例运行效果图如下,只有点击Scale按钮时SpecialImage产生刷新现象,点击Move按钮时SpecialImage不会刷新。
图4 修改代码后点击Scale按钮和Move按钮时运行动图

可以使用上文步骤再次获取ComponentA组件的状态变量信息如下,可以看到ComponentA中状态变量scaleStyle影响组件SpecialImage[74]和Image[78],状态变量translateStyle影响组件Stack[76],translateStyle的变化不会再导致SpecialImage的刷新。
--------------ViewPU State Variables--------------
[-stateVariables, viewId=70, isRecursive=false]
|--ComponentA[70]
@ObjectLink (class SynchedPropertyNestedObjectPU) 'scaleStyle'[71]
|--Owned by @Component 'ComponentA'[70]
|--Sync peers: {
@Link/@Consume (class SynchedPropertyTwoWayPU) 'specialImageScaleStyle'[75] <@Component 'SpecialImage'[74]>
}
|--Dependent components: 1 elmtIds: 'Image[78]'
@ObjectLink (class SynchedPropertyNestedObjectPU) 'translateStyle'[72]
|--Owned by @Component 'ComponentA'[70]
|--Sync peers: none
|--Dependent components: 1 elmtIds: 'Stack[76]'

更多推荐
所有评论(0)