HarmonyOS Performance Optimization Treasures: Practical Case Analysis of Frame Rate Issues
Hey, HarmonyOS developers! Today I’m sharing a big discovery from development—there are a bunch of super practical performance optimization cases hidden in the official HarmonyOS documentation! These cases not only solve common dropped frame and stutter issues, but also come with detailed analysis ideas and code refactoring solutions. I’ve organized several high-frequency scenarios, explained with code, to help you thoroughly master smoothness optimization!
1. Long List Scrolling Lag Optimization
Problem Phenomenon:
On the „HMOS World“ homepage, after loading 1000 pieces of data, scrolling becomes increasingly laggy, with a dropped frame rate of 7%.
Analysis Tools:
- AppAnalyzer: Detected excessive scroll lag rate (>5ms/s).
-
Frame Profiler: Trace recording found that the
BuildLazyItem
method took up 52.7% of the time, and theArticleCardView
component was frequently rebuilt.
Key Code (Before Optimization):
@Component
struct ArticleCardView {
@Prop item: ArticleData; // Deep copy causes performance loss
build() {
Row() {
// Nested complex layout
ActionButtonView({ icon: this.item.icon }) // Child component uses @Prop
}
}
}
@Component
struct ActionButtonView {
@Prop icon: Resource; // Deep copy every time parent updates
// ...
}
Problem Location:
- The
@Prop
decorator deep copies complex objects, causing a surge in component creation time. - List items are not reused, and components are rebuilt every time you scroll.
Optimization Plan:
-
Component Reuse: Use
@Reusable
to cache components and reduce rebuild overhead. -
Replace @prop: Use
@Builder
to construct lightweight subcomponents and avoid deep copying.
Optimized Code:
@Reusable // Enable component reuse
@Component
struct ArticleCardView {
aboutToReuse(params: Record<string, Object>) { // Reuse callback
this.item = params.item as ArticleData;
}
build() {
Row() {
ActionButtonBuilder({ icon: this.item.icon }) // Use Builder instead
}
}
}
// Use Builder instead of @Component
@Builder
function ActionButtonBuilder(icon: Resource) {
Button(icon)
.width(40)
.height(40)
}
Effect: Dropped frame rate reduced from 7% to 0%!
2. Custom Animation Dropped Frames
Problem Phenomenon:
Handwritten animation logic results in only 63fps (device supports 120Hz).
Problem Code:
computeSize() {
// Manually calculate properties for each frame (bad example!)
for (let i = 1; i <= doTimes; i++) {
setTimeout(() => {
this.heightSize += deltaHeight;
this.widthSize += deltaWeight; // Frequent calculations on main thread
}, i * period);
}
}
Reason:
Loop calculations block the main thread, making it impossible to complete rendering within 8.3ms (120Hz).
Optimization Plan:
Use the system animation API—GPU automatically interpolates calculations, freeing the main thread.
Optimized Code:
Button('click me')
.onClick(() => {
this.widthSize = this.flag ? 100 : 200;
this.heightSize = this.flag ? 50 : 100;
this.flag = !this.flag;
})
.animation({ // System property animation
duration: 2000,
curve: Curve.Linear,
delay: 500
})
Effect: Frame rate increased to 116.9fps!
3. Excessive Layout Nesting
Problem Phenomenon:
List items nested with 20 layers of Stack
, Measure
layout time is excessive.
Analysis Tools:
- ArkUI Inspector: Visually view the component tree and locate redundant nesting.
-
Frame Profiler:
FlushLayoutTask
takes up more than 70% of the time.
Structure Before Optimization:
@Reusable
@Component
struct ChildComponent {
build() {
Stack() {
Stack() {
Stack() { /* Nested 20 layers... */ }
}
}
}
}
Optimization Plan:
- Remove meaningless nesting and use
RelativeContainer
to replace multipleStack
layers. - Simplify component styles and merge properties.
Optimized Code:
@Reusable
@Component
struct ChildComponent {
build() {
RelativeContainer() { // Use relative layout instead of Stack
Text(this.item)
.fontSize(50)
.margin({ left: 10, right: 10 })
.alignRules({ top: { anchor: "__container__", align: VerticalAlign.Top } })
}
}
}
Effect: Layout time reduced by 60%, smooth scrolling.
4. Time-Consuming Operations on the Main Thread
High-Frequency Pitfalls:
- Synchronously reading large files in
onClick
. - Real-time data calculation during list scrolling.
Optimization Tips:
// Wrong! Synchronous IO on the main thread
onClick(() => {
let data = fs.readFileSync('huge_data.json'); // Blocks rendering
})
// Correct approach → Hand off to Worker thread
onClick(() => {
const worker = new worker.ThreadWorker('workers/io.js');
worker.postMessage('huge_data.json');
})
Key Principle:
The main thread should only handle lightweight operations: UI updates and gesture responses.
Time-consuming tasks (IO/calculation) should be handed over to Worker or async queues.
5. Other Golden Optimization Suggestions
-
State Management:
- Use
@ObjectLink
instead of@Prop
to reduce deep copying. - Partial refresh: Use
@State
variables to control the update range of subcomponents.
- Use
-
List Performance:
- Tune the
cachedCount
preload number ofLazyForEach
(recommended 5~10). - For complex list items, use
@Reusable
+aboutToReuse
for reuse.
- Tune the
-
GPU Load:
- Reduce overlay of transparent layers (avoid overusing
opacity
). - Match image size to display area to avoid memory waste.
- Reduce overlay of transparent layers (avoid overusing
Conclusion
After digging deep into the HarmonyOS documentation, I found that the official docs actually hide a lot of „treasure cases“ for performance optimization. In actual development, frame rate issues are mainly concentrated in main thread blocking, long rendering pipelines, and GPU overload. Make good use of Frame Profiler
+ ArkUI Inspector
, and combine today’s code refactoring ideas to easily achieve a silky-smooth 120fps experience!
Encountered other pitfalls? Feel free to discuss in the comments — and don’t forget to ask questions in the HarmonyOS developer community, the official team responds super quickly!
Let’s work hard on HarmonyOS and be the coolest developers! 💪