HarmonyOS 鸿蒙Next LazyForEach + Lottie动画 内存占用过大
HarmonyOS 鸿蒙Next LazyForEach + Lottie动画 内存占用过大
参考 [如何定位列表中Canvas加载Lottie动画卡顿的问题-测试与调优-常见问题手册 - 华为HarmonyOS开发者 (huawei.com)] 编码。
用profiler观察滑动时的实时内存变化,发现内存占用过大。搞不清楚是代码原因还是GC不足。
根据官方文档编写如下两个文件代码于pages目录,lottie动画路径请自行添加:
/**
* BasicDataSource代码见文档末尾附件: string类型数组的BasicDataSource代码
*/
import { NineListItem } from './NineListItem';
// BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
private originDataArray: string[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): string {
return this.originDataArray[index];
}
// 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
// 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
// 通知LazyForEach组件需要重载所有子组件
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
// 通知LazyForEach组件需要在index对应索引处添加子组件
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
// 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]);
})
}
// 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
// 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]);
})
}
// 通知LazyForEach组件需要在index对应索引处删除该子组件
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
// 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]);
})
}
// 通知LazyForEach组件将from索引和to索引处的子组件进行交换
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
// 写法2:listener.onDatasetChange(
// [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}});
})
}
notifyDatasetChange(operations: DataOperation[]): void {
this.listeners.forEach(listener => {
listener.onDatasetChange(operations);
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string[] = [];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): string {
return this.dataArray[index];
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
declare class ArkTools {
static hintGC(): void;
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
aboutToAppear() {
for (let i = 0; i <= 50; i++) {
this.data.pushData(`Hello ${i}`)
}
}
build() {
Column(){
Button("触发HintGC").onClick((event: ClickEvent) => {
ArkTools.hintGC(); //方法内直接调用
})
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
NineListItem({index:item})
}, (item: string) => item)
}.cachedCount(2)
}
}
}
import Lottie, { AnimationItem } from '@ohos/lottie'
@Reusable
@Component
export struct NineListItem {
@State @Require index: string = 'a';
@State isPlaying: boolean = false;
private mainRenderingSettings: RenderingContextSettings = new RenderingContextSettings(true)
private mainCanvasRenderingContext: CanvasRenderingContext2D =
new CanvasRenderingContext2D(this.mainRenderingSettings)
private animateName: string = "homeLiveFeedAnimate" + (Math.random() * 1000).toFixed()
private animateItem: AnimationItem | null = null;
aboutToDisappear(): void {
// 组件销毁时先释放动画对象
this.animateItem?.destroy();
this.animateItem = null;
Lottie.destroy(this.animateName)
}
build() {
Row() {
Canvas(this.mainCanvasRenderingContext)
.width(200)
.height(200)
.backgroundColor(Color.Gray)
.onReady(() => {
this.animateItem?.destroy();
// 加载动画
if (this.animateItem != null) {
// 可在此生命回调周期中加载动画,可以保证动画尺寸正确
this.animateItem?.resize();
} else {
// 抗锯齿的设置
this.mainCanvasRenderingContext.imageSmoothingEnabled = true;
this.mainCanvasRenderingContext.imageSmoothingQuality = 'medium'
this.animateItem = Lottie.loadAnimation({
container: this.mainCanvasRenderingContext,
renderer: 'canvas', // canvas 渲染模式
loop: true,
autoplay: false,
name: this.animateName,
contentMode: 'Contain',
path: "pages/lottie/animation.json",
})
this.animateItem.changeColor([225, 25, 100, 1]);
this.animateItem.play()
}})
Text("Hello!!")
}
}
}
更多关于HarmonyOS 鸿蒙Next LazyForEach + Lottie动画 内存占用过大的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
治标不治本思路:
该思路停止了不可见动画的后台播放,但是没有销毁掉对应item,不过也应该够用了?
参考 Lottie动画在列表中滚动卡顿如何优化-ArkUI开发框架-开发-常见问题手册 - 华为HarmonyOS开发者 (huawei.com)
添加如下代码至 NineListItem.ets 的 Canvas组件。当不可见时停止动画播放。
Canvas(this.mainCanvasRenderingContext)
.onVisibleAreaChange([0.0, 1.0],
(isVisible: boolean, currentRatio: number) => {
if (isVisible) {
console.log('onVisibleAreaChange:' + 'play' + ' index: ' + this.index)
this.animateItem?.play();
} else {
console.log('onVisibleAreaChange:' + 'stop' + ' index: ' + this.index)
this.animateItem?.stop();
}
})
更多关于HarmonyOS 鸿蒙Next LazyForEach + Lottie动画 内存占用过大的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
使用LazyForEach的list 要指定高度,比如.height(‘100%’),否则懒加载不会生效的。
我认为关键点不在 .height(100%")
设置上。
我的观察是提问中的程序还是执行了懒加载的,应用冷启动后初始内存90MB左右。在50个item时内存占用 420MB左右。
不断上下滑动屏幕,峰值内存会飙到500多MB,但最后常驻会在420左右。
我的感觉是懒加载后,屏幕不可见部分的item没有被销毁。
提问:1. 懒加载把列表加载全后不会销毁之间加载过但是不可见的items吗? 2. 如何成功销毁掉不可见部分的lottie items?
针对HarmonyOS鸿蒙系统中使用Next LazyForEach与Lottie动画导致的内存占用过大问题,可能的原因及解决方案简述如下:
在鸿蒙系统中,Next LazyForEach用于高效渲染列表项,而Lottie动画则负责动态效果展示。当这两者结合使用时,若处理不当,确实可能导致内存占用显著增加。
内存占用过大可能源于多个方面:
-
动画资源过大:Lottie动画文件若过于复杂或尺寸过大,会直接导致内存消耗增加。优化动画文件,减少不必要的细节和帧数,有助于降低内存占用。
-
列表项缓存:Next LazyForEach虽然旨在按需加载列表项,但若未正确管理缓存,可能导致已渲染项在内存中持续占用空间。检查并确保列表项的缓存策略合理,避免不必要的内存泄漏。
-
内存泄漏:代码中的内存泄漏同样会导致内存占用不断攀升。使用鸿蒙提供的内存分析工具,定期检查并修复潜在的内存泄漏问题。
综上所述,针对内存占用过大问题,建议从优化动画资源、管理列表项缓存以及排查内存泄漏等方面入手。若问题依旧无法解决,请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html