前言
最近做资源的重新加载和释放的时候发现了一个问题,在切换场景的时候去释放所有资源,在转场成功后发现有部分字体的纹理没有被释放,进一步调查,发现没有被释放的问题都是csb内创建的font字体,所以本篇文章记录了查找内存泄漏的过程和解决方案
找bug过程
重写CCTexure2D的retain和release
在retain通过断点查看堆栈,可以看到font的png在创建后一共经过三次retain 通过csb创建的字体和手动create的字体没有区别,都是这三次
-
release
-
create代码创建
-
csb创建
- TextureAtlas::~TextureAtlas()
在release内断点的堆栈可以看出csb创建的font字体少了一个FontAtlas内释放Texture的过程
FontAtlas查找纹理释放
void FontAtlas::releaseTextures()
{
for( auto &item: _atlasTextures)
{
item.second->getPath().c_str());
item.second->release();
}
_atlasTextures.clear();
}
在fontatlas的releaseTextures内打断点,发现根本没有调用
FontAtlas::~FontAtlas()
{
......
releaseTextures();
......
}
releaseTextures在FontAtlas的析构函数被调用,打断点发现析构函数没有被调用,也就是说FontAtlas没有被释,这里初步怀疑FontAtlas在创建的时候被多引用了
查找FontAtlas的释放
Label::~Label()
{
......
if (_fontAtlas)
{
......
FontAtlasCache::releaseFontAtlas(_fontAtlas);
}
......
}
通过堆栈信息找到,FontAtlas的释放是在Label的析构函数内,在这里打断点,发现果然,_fontAtlas通过csb创建的时候引用计数是2,通过create手动创建引用计数是1
查找FontAtlas的创建
FontAtlas* FontAtlasCache::getFontAtlasFNT(const std::string& fontFileName, const Vec2& imageOffset )
{
......
auto it = _atlasMap.find(atlasName);
if ( it == _atlasMap.end() )
{
auto font = FontFNT::create(realFontFilename, imageOffset);
if(font)
{
auto tempAtlas = font->createFontAtlas();
if (tempAtlas)
{
_atlasMap[atlasName] = tempAtlas;
return _atlasMap[atlasName];
}
}
}
else
{
_atlasMap[atlasName]->retain();
return _atlasMap[atlasName];
}
return nullptr;
}
FontAtlas通过FontAtlasCache的getFontAtlasFNT来获取和创建 如果FontAtlas从没有创建过,那么就create一个新的放到_atlasMap里缓存,默认创建后引用计数1 如果FontAtlas创建过,从_atlasMap里取出来,引用计数加1 在这里打断点,发现create创建的font字体只调用了getFontAtlasFNT了一次,csb创建的font字体调用了两次
查找getFontAtlasFNT被调用两次的原因
void TextBMFontReader::setPropsWithFlatBuffers(cocos2d::Node *node, const flatbuffers::Table *textBMFontOptions)
{
......
switch (cmfType)
{
case 0:
{
if (FileUtils::getInstance()->isFileExist(path))
{
FontAtlas* newAtlas
###### FontAtlasCache::getFontAtlasFNT(path); ######
if (newAtlas)
{
fileExist = true;
}
else
{
errorContent = "has problem";
fileExist = false;
}
}
break;
}
default:
break;
}
if (fileExist)
{
###### labelBMFont->setFntFile(path); ######
}
......
}
通过断点的堆栈信息查看,csb在创建字体的两次调用,在TextBMFontReader的setPropsWithFlatBuffers方法内,被我用######标记了出来
if (FileUtils::getInstance()->isFileExist(path))
{
FontAtlas* newAtlas
###### FontAtlasCache::getFontAtlasFNT(path); ######
if (newAtlas)
{
fileExist = true;
}
else
{
errorContent = "has problem";
fileExist = false;
}
}
break;
}
setPropsWithFlatBuffers方法内的第一处调用,这里应该是想通过看能不能创建atlas来判断.fnt文件是否有问题,但是只要是调用getFontAtlasFNT默认就已经放到cache文件的_atlasMap里了,引用计数为1
if (fileExist)
{
###### labelBMFont->setFntFile(path); ######
}
void TextBMFont::setFntFile(const std::string& fileName)
{
......
_labelBMFontRenderer->setBMFontFilePath(fileName);
......
}
bool Label::setBMFontFilePath(const std::string& bmfontFilePath, const Vec2& imageOffset, float fontSize)
{
FontAtlas *newAtlas = FontAtlasCache::getFontAtlasFNT(bmfontFilePath,imageOffset);
if (!newAtlas)
{
reset();
return false;
}
......
return true;
}
第二处调用,如果atlas能创建成功,就调用了Label的setBMFontFilePath方法,很不幸,在setBMFontFilePath方法内又调用了一遍getFontAtlasFNT,这就造成一个字体在创建的时候引用了两次
问题总结
通过上述的过程,发现问题出现在通过csb创建font字体的时候,相关的数据文件在创建后被多引用了一次,造成切场景是,label不能把atlas释放掉,结果atlas下关联的字体texture的引用计数也不会被减少
解决思路
手动释放一次atlas
void TextBMFontReader::setPropsWithFlatBuffers(cocos2d::Node *node, const flatbuffers::Table *textBMFontOptions)
{
......
FontAtlas* newAtlas = nullptr;
switch (cmfType)
{
case 0:
{
if (FileUtils::getInstance()->isFileExist(path))
{
newAtlas = FontAtlasCache::getFontAtlasFNT(path);
if (newAtlas)
{
fileExist = true;
}
else
{
errorContent = "has problem";
fileExist = false;
}
}
break;
}
default:
break;
}
if (fileExist)
{
labelBMFont->setFntFile(path);
FontAtlasCache::releaseFontAtlas(newAtlas);
}
......
}
第一种修改方式,在labelBMFont->setFntFile(path);后手动释放一下新创建的tlas
少调用一次
void TextBMFontReader::setPropsWithFlatBuffers(cocos2d::Node *node, const flatbuffers::Table *textBMFontOptions)
{
......
switch (cmfType)
{
case 0:
{
if (FileUtils::getInstance()->isFileExist(path))
{
labelBMFont->setFntFile(path);
}
break;
}
default:
break;
}
......
}
直接舍弃掉atlas的创建判断过程,在用的时候直接创建
推送
https://github.com/KingSun5
结语
虽然问题解决了,但是不太清楚是不是当时写框架的人有别的考量的地方,如果有同学知道有博主没有考虑到的地方,欢迎留言交流,最后希望看到最后的同学有所收获,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。 QQ交流群:806091680(Chinar) 该群为CSDN博主Chinar所创,推荐一下!我也在群里! 本文属于原创文章,转载请著名作者出处并置顶!!
|