鸿蒙 APP 还是卡顿?API 21 性能优化这 3 招,立竿见影!
摘要:本文针对鸿蒙API 21开发中常见的性能问题,提出三大优化方案。首先推荐使用LazyForEach替代ForEach实现列表懒加载,避免一次性渲染所有数据导致内存爆炸;其次建议采用TaskPool任务池机制,将复杂计算任务移至后台线程,避免阻塞UI主线程;最后强调组件化开发规范,通过合理拆分UI组件提升渲染效率。文章包含完整代码示例,重点解决真机运行卡顿、内存占用高等实际问题,帮助开发者快速
Hello,兄弟们,我是 V 哥!
昨天有个粉丝在群里哭诉:“V 哥,我用鸿蒙 API 21 写的 App,在模拟器上跑得像法拉利,一到真机老款机型上,划一下屏幕顿两下,简直像在开拖拉机!产品经理都快把我的键盘砸烂了!”
我心想,有没有可能不是手机不行,这是代码没写对呢!
很多兄弟从 Android 或者 Vue 转过来,习惯性地把以前那套“暴力渲染”的逻辑搬到 ArkTS 上。在 API 21 这个新版本上,鸿蒙的渲染引擎虽然强,但你不按它的套路出牌,它照样给你摆烂。
今天,V 哥就掏出压箱底的**“性能三板斧”**。这三招,只要你能消化哪怕一招,你的 App 流畅度立马提升一个档次。咱们直接上 DevEco Studio 6.0 的实战代码,开整!
第一招:长列表别用 ForEach,LazyForEach 才是YYDS
痛点在哪?
很多兄弟写列表,习惯性上 ForEach。V 哥必须提醒你:ForEach 是一次性渲染。如果你的数据有几百条、几千条,它会啪一下一下子把所有组件全创建出来。内存瞬间爆炸,CPU 飙升,卡顿是必然的!
解决方案
API 21 下,必须要用 LazyForEach(懒加载)。它的核心逻辑是:只渲染屏幕可见的那几个 Item,你滑下来一个,我再创建一个,滑上去销毁一个。内存占用极低,丝般顺滑。
代码实战
兄弟们,这部分代码比较经典,建议直接复制到你的 DevEco Studio 里跑一跑。
// 1. 定义基础的数据源接口。这是 LazyForEach 必须要实现的规矩
interface IBasicDataSource {
totalCount(): number;
getData(index: number): Object;
registerDataChangeListener(listener: IDataChangeListener): void;
unregisterDataChangeListener(listener: IDataChangeListener): void;
}
// 2. 重命名以避免冲突 - 修复第10行错误
interface IDataChangeListener {
onDataReloaded(): void;
onDataAdded(index: number): void;
onDataChanged(index: number): void;
onDataDeleted(index: number): void;
onDataMoved(from: number, to: number): void;
}
// 3. 实现数据变化监听器 - 使用新名称
class DataChangeCallback implements IDataChangeListener {
onDataReloaded(): void {}
onDataAdded(index: number): void {}
onDataChanged(index: number): void {}
onDataDeleted(index: number): void {}
onDataMoved(from: number, to: number): void {}
}
// 4. 核心数据源类(V哥精简版)
class MyDataSource implements IBasicDataSource {
private listeners: IDataChangeListener[] = [];
private dataList: string[] = [];
constructor(list: string[]) {
this.dataList = list;
}
totalCount(): number {
return this.dataList.length;
}
getData(index: number): Object {
return this.dataList[index];
}
registerDataChangeListener(listener: IDataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: IDataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
public addData(data: string) {
this.dataList.push(data);
this.notifyDataReloaded();
}
private notifyDataReloaded() {
this.listeners.forEach(listener => {
listener.onDataReloaded();
});
}
}
// 简化数据变更监听器
class SimpleDataChangeCallback extends DataChangeCallback {
onDataReloaded(): void {
console.log("数据已重新加载,UI可以刷新");
}
}
@Entry
@Component
struct LazyForEachDemo {
@State dataSource: MyDataSource = new MyDataSource([]);
// 使用新的监听器类型
private listener: SimpleDataChangeCallback = new SimpleDataChangeCallback();
aboutToAppear() {
// 预先生成数据
const initData: string[] = [];
for (let i = 0; i < 1000; i++) {
initData.push('V哥带你飞 - 第 ' + (i + 1) + ' 条数据');
}
this.dataSource = new MyDataSource(initData);
// 注册数据变化监听器
this.dataSource.registerDataChangeListener(this.listener);
}
aboutToDisappear() {
// 取消注册数据变化监听器
this.dataSource.unregisterDataChangeListener(this.listener);
}
build() {
Column() {
// 标题
Text('LazyForEach 性能演示')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin(10)
List({ space: 5 }) {
LazyForEach(
this.dataSource,
(item: string, index?: number) => {
ListItem() {
this.ListItemChild(item)
}
},
(item: string, index?: number) => {
// 返回索引作为唯一标识
if (index === undefined) {
return Math.random().toString();
}
return index.toString();
}
)
}
.width('100%')
.height('85%')
.layoutWeight(1)
Button('模拟增加数据')
.onClick(() => {
this.dataSource.addData('新数据 ' + (this.dataSource.totalCount() + 1));
})
.margin(10)
.width('50%')
}
.width('100%')
.height('100%')
}
// 将子组件改为build方法内的组件构建器
@Builder
ListItemChild(content: string) {
Row() {
Text(content)
.fontSize(14)
.flexGrow(1)
.textAlign(TextAlign.Start)
.padding(10)
}
.width('100%')
.height(60)
.backgroundColor('#f0f0f0')
.borderRadius(8)
.margin({ left: 10, right: 10, top: 2, bottom: 2 })
}
}
V 哥划重点:
- 千万别懒,一定要实现
IDataSource。 LazyForEach的第三个参数(key生成函数)一定要写,而且要保证唯一性!这是组件复用的身份证,写错了渲染必乱。

第二招:别在主线程算数,TaskPool 帮你搬砖
痛点在哪?
你是不是经常在点击事件里直接写大量逻辑?比如解析巨大的 JSON、图片滤镜处理、复杂算法排序?兄弟,那是主线程(UI线程)啊! 你在那算数,UI 就得等着,屏幕当然卡死不动。
解决方案
API 21 推荐使用 TaskPool(任务池)。把重活累活扔给后台线程池去干,算完了结果一扔,主线程只负责展示。分工明确,效率翻倍。
代码实战
咱们模拟一个“复杂排序”的场景,看 V 哥怎么用 TaskPool 优化。
import taskpool from '@ohos.taskpool';
// 1. 定义一个并发函数(这是在后台线程跑的)
// 注意:@Concurrent 装饰器是必须的,这是 ArkTS 并发编程的标识
@Concurrent
function heavyComputation(data: number[]): number[] {
// V 哥模拟一个超级耗时的排序操作
// 比如这里可以换成复杂的 JSON 解析、加密解密等
let arr = [...data];
arr.sort((a, b) => a - b);
// 模拟耗时,让兄弟们看到效果
let start = new Date().getTime();
while (new Date().getTime() - start < 500) {
// 故意卡住 500毫秒,如果在主线程,UI会完全冻结
}
console.info("V哥后台线程计算完毕!");
return arr;
}
@Entry
@Component
struct TaskPoolDemo {
@State message: string = '点击按钮开始计算';
@State resultString: string = '结果等待中...';
@State isCalculating: boolean = false;
build() {
Column() {
Text(this.message)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ top: 20, bottom: 20 })
if (this.isCalculating) {
LoadingProgress()
.width(50)
.height(50)
.color(Color.Blue)
} else {
Text(this.resultString)
.fontSize(16)
.fontColor(Color.Gray)
.margin({ bottom: 20 })
}
Button('使用 TaskPool 后台计算')
.enabled(!this.isCalculating)
.onClick(() => {
this.startCalculation();
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(20)
}
// 将计算逻辑提取为独立方法
private async startCalculation(): Promise<void> {
this.isCalculating = true;
this.message = "正在后台拼命算数中...";
try {
// 准备一些乱序数据
let rawData: number[] = [];
for(let i = 0; i < 10000; i++) {
rawData.push(Math.random() * 10000);
}
// 修复:使用正确的 TaskPool API
// 方式1:直接执行函数(推荐)
const result = await taskpool.execute(heavyComputation, rawData) as number[];
// 计算完成,回到主线程(这里会自动切回来,放心用UI)
this.isCalculating = false;
this.message = "计算完成!UI丝滑不卡顿!";
this.resultString = `前5个数据: ${result.slice(0, 5).join(', ')}`;
} catch (err) {
this.isCalculating = false;
console.error(`V哥报错: ${JSON.stringify(err)}`);
this.message = "计算失败!";
this.resultString = `错误信息: ${err}`;
}
}
}
这个案例需要真机测试,V 哥使用新入手的MatePad Pro:
以下是单击按钮后运行的结果:
V 哥划重点:
- 记得给函数加
@Concurrent,否则扔不进 TaskPool。 - TaskPool 是自动管理线程的,你别自己 new Thread,那样太低级且容易OOM。
- 记住,UI 只能更新状态,不能做重活。
第三招:组件别总造新的,@Reusable 复用才省钱
痛点在哪?
在列表滑动或者页面切换时,如果频繁创建和销毁组件(比如 new ChildComponent()),GC(垃圾回收)压力会非常大,导致内存抖动,表现就是掉帧。
解决方案
API 21 提供了一个非常强力的装饰器:@Reusable。它的作用是:组件不从树上卸载,而是回收到缓存池里,下次需要的时候直接拿过来改个数据接着用。这简直是“物尽其用”的典范!
代码实战
咱们看怎么改造刚才的 ListItemChild 组件。
// 定义一个复用的数据模型,方便传递
class ListItemParams {
content: string = "";
color: string = "#ffffff";
}
@Entry
@Component
struct ReusableDemo {
// 模拟数据
private dataList: ListItemParams[] = [];
aboutToAppear() {
// 在生命周期中初始化数据,避免在构建时执行复杂逻辑
for (let i = 0; i < 100; i++) {
let item = new ListItemParams();
item.content = `可复用组件 Item ${i + 1}`;
item.color = i % 2 === 0 ? '#e0e0e0' : '#ffffff';
this.dataList.push(item);
}
}
build() {
Column() {
Text('Reusable 组件演示')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin(10)
List() {
ForEach(this.dataList, (item: ListItemParams) => {
ListItem() {
// 使用我们的复用组件
ReusableChild({ param: item })
}
}, (item: ListItemParams) => item.content + Math.random()) // 唯一Key,避免使用index
}
.width('100%')
.height('90%')
.layoutWeight(1)
.scrollBar(BarState.Off)
}
.width('100%')
.height('100%')
}
}
// 核心重点:可复用组件
@Component
struct ReusableChild {
// 使用 @Prop 装饰器来接收父组件传递的参数
@Prop param: ListItemParams;
// 组件自己的状态
@State private reuseCount: number = 0;
/**
* 生命周期:当组件从缓存池被重新拿出来复用时触发
* 注意:ArkTS 中正确的复用生命周期是 aboutToReuse
*/
myAboutToReuse(param: ListItemParams): void {
// 更新参数
this.param = param;
this.reuseCount++;
console.info(`V哥:组件被复用了!复用次数: ${this.reuseCount}`);
}
build() {
Row() {
Text(this.param.content)
.fontSize(16)
.fontColor(Color.Black)
.flexGrow(1)
Blank()
Column() {
Text('复用组件')
.fontSize(10)
.fontColor(Color.Gray)
Text(`${this.reuseCount > 0 ? '已复用' : '新建'}`)
.fontSize(10)
.fontColor(this.reuseCount > 0 ? Color.Green : Color.Blue)
}
}
.width('100%')
.height(60)
.backgroundColor(this.param.color)
.padding({ left: 15, right: 15 })
.borderRadius(8)
.alignItems(VerticalAlign.Center)
}
}

V 哥划重点:
- 加上
@Reusable装饰符,你的组件就开启了“绿色环保”模式。 - 必须实现
aboutToReuse方法。这是复用组件的灵魂,它决定了你把旧组件拿回来后,怎么给它“洗心革面”(更新数据)。 - 配合 LazyForEach 使用,那是绝配,性能起飞!
V 哥总结一下
兄弟们,API 21 的鸿蒙开发,其实就是在跟**“渲染”和“资源”**打交道。
- 长列表?上
LazyForEach,按需加载。 - 重任务?上
TaskPool,后台多线程。 - 组件多?上
@Reusable,回池复用。
这三招你哪怕只学会了一招,你那个像“拖拉机”一样的 App 也能立马变“法拉利”。V 哥话就撂这儿了,代码都给你整理好了,直接去 DevEco Studio 6.0 里敲一遍,感受一下那种丝滑的快感!
我是 V 哥,咱们下期技术复盘见!别忘了给文章点个赞,这是 V 哥持续输出的动力!👋
更多推荐


所有评论(0)