Egret引擎内存优化最佳实践
最近我们在开发一款服饰类的中度游戏项目,里面用到了大量的服饰图片。游戏测试过程中发现内存一直在不断增长,在部分低端机型下出现闪退现象,下面来说说解决过程。
毫无疑问,这是一个对内存进行有效管理的问题。明确了方向,那我们就从下几步进行排查: 1、排查是否存在内存泄露,看js对象的创建后是否有删除引用,是否有移除侦听; 2、使用对象池,减少重复对象的频繁创建 3、优化图片素材尺寸,减少不必要图片的加载 这样改完之后,测试发现效果并不明显,内存依旧会不断增加。
-
难道是图片内存没有释放? 看着这么大的内存,基本可以明确是图片的原因
-
那图片内存要怎样才能释放? 检查代码已经没有地方对图片进行引用了,按理说应该会被垃圾回收器回收啊!
-
怎么办? 查引擎文档,发现有个RES.destroyRes的API,作用是:销毁单个资源文件或一组资源的缓存数据。 再阅读源码,发现Egret引擎会对资源进行引用,如果不调用RES.destroyRes进行资源释放,图片内存不会进行回收! 结论:通过引擎加载的所有图片,如果不手动销毁对象,那么图片会一直缓存在内存里面。加载的图片越多,内存占用越大,直到闪退。Egret引擎本身没有任何内存管理策略。
-
再接下来怎么办? 知道了原因,就好办了,针对每个功能模块,在合适的位置,释放图片资源。 游戏素材一般包括以下这些: 1、 UI图片,比如弹窗、界面、按钮,一般打包进代码包 2、 远程加载的图片,比如大背景,根据用户数据特定加载显示的图片 3、 Mp3声音文件
我们制定了以下资源内存管理的策略:
- 针对大的UI素材 png、jpg等,针对每个功能模块,在界面关闭的时候 调用 destroyImage释放图片资源。
- 针对 远程http的的图片, A、非列表item使用的图片用 reSetImage方法代替设置img.source, 同时关闭界面时 调用destroyImage释放图片资源。 B、滚动列表里面用到的图片 用HttpImageCacheManage 管理,在关闭界面时释放图片资源。
- mp3声音文件,针对每个功能模块,在关闭界面时释放
下面为几个核心方法的代码:
/**
* 释放资源,一般是释放 eui.image的资源
* 使用场景: 在界面关闭的时候调用;
* 使用示例:destroyImage(this.bg);
* 使用建议:尺寸大于1024的图片对象调用本方法释放掉
* 参数:支持 资源名,url,或image对象
*/
public destroyImage(name:string | eui.Image){
if(name instanceof eui.Image){
if(name.source){
let source = name.source;
if(DEBUG) console.warn("释放:" + source)
name.source = "";
if(RES.getRes(<any>source))
return RES.destroyRes(<any>source);
}
return false;
}
if(DEBUG) console.warn("释放:" + name)
if(RES.getRes(<any>name))
return RES.destroyRes(<any>name);
return false;
}
/*
* 释放Image资源,并设置image新的source
* 使用场景: 用本方法 替代 img.source = xxxx;
* 使用示例:reSetImage(this.bg, xxxx);
* 使用建议:尺寸大于512的,或使用过1次后不再使用的图片对象调用本方法释放掉
*/
public reSetImage(name:eui.Image, newSource:string, nowDestroy:boolean=false){
if(!name) return null;
if(name.source){
let source = <string>name.source;
if(source != newSource){
if(DEBUG) console.warn("释放:" + source)
if(nowDestroy){
if(RES.getRes(source))
RES.destroyRes(source);
}
else{
name.once(egret.Event.COMPLETE, ()=>{
if(RES.getRes(source))
RES.destroyRes(source);
}, this)
name.source = newSource;
}
}
return;
}
name.source = newSource;
}
enum HttpImageTYPE { type1, type2, type3}; //图片类型分类
/**
* 图片缓存管理:针对部分不适合在更换source时释放的Http图片,采用标记的形式管理,在界面关闭的时候释放
* 使用方法:
* item里面: HttpImageCacheManage.add(HttpImageTYPE.type1, url);
* ui.hide里面: HttpImageCacheManage.destroy(HttpImageTYPE.type1);
* */
class HttpImageCacheManage {
private static pools = {};
public constructor() {
}
//把素材按类型添加到列表中,方便后面释放
public static add(type:number, url:string){
if(!this.pools[type]) this.pools[type] = {};
if(!this.pools[type][url])
this.pools[type][url] = 1;
}
//根据type类型释放对应的素材
public static destroy(type:number){
let list = this.pools[type];
for(var key in list){
let texture = RES.getRes(key);
if(texture && texture.$bitmapData){
let hashCode = texture.$bitmapData.hashCode;
var tempList = egret.BitmapData["_displayList"][hashCode];
if(tempList && tempList.length > 0) continue; //表示正在使用
}
if(DEBUG) console.warn("释放:" + key)
RES.destroyRes(key);
delete list[key]
}
if(DEBUG){
if(HttpImageCacheManage["dtime"]) egret.clearTimeout(HttpImageCacheManage["dtime"]);
HttpImageCacheManage["dtime"] = egret.setTimeout(()=>{
console.warn("释放资源后内存占用:");
RES.profile();
}, this, 20);
}
}
}
请特别注意的是: 在释放图片资源时需要判断图片是否正在使用,比如A界面用到了图片1,B界面也用到了图片1,B界面关闭的时候就不能把图片1释放掉,图片资源(egret.Texture)在内存里面是共用的,如果释放了,在A界面图片就显示不出来了。
Egret引擎没有提供egret.Texture 是否正在使用的方法,需要我们自己读取私有变量来判断。
public static checkIsUsing(resName:string){
let texture = RES.getRes(resName);
if(texture && texture.$bitmapData){
let hashCode = texture.$bitmapData.hashCode;
var tempList = egret.BitmapData["_displayList"][hashCode];
if(tempList && tempList.length > 0)
return true; //表示正在使用
}
return false;
}
通过以上策略的使用,图片内存得到了有效管理,再重度的游戏,都可以在低端机上正常运行。而且,代码修改成本很低!
最后,以上方法是基于 Egret引擎RES组件管理素材。有些开发者没有使用RES来管理素材,而是直接使用 egret. URLLoader 或 egret. ImageLoader 来加载的图片素材,这种情况需要怎样释放图片内存呢? 答:只需要针对不用的图片资源(egret.Texture)调用dispose方法、并删除引用就ok了!
本文未经允许,禁止转载~~