zl程序教程

您现在的位置是:首页 >  硬件

当前栏目

【cocos2dx】记录解决csb创建font字体造成的内存泄漏问题

内存 解决 创建 记录 字体 泄漏 造成 cocos2dx
2023-09-11 14:19:58 时间

前言

最近做资源的重新加载和释放的时候发现了一个问题,在切换场景的时候去释放所有资源,在转场成功后发现有部分字体的纹理没有被释放,进一步调查,发现没有被释放的问题都是csb内创建的font字体,所以本篇文章记录了查找内存泄漏的过程和解决方案

找bug过程

重写CCTexure2D的retain和release

  • retain

    • TextureCache::AddImage

    • TextureAtlas::initWithTexture

    • FontAtlas::addTexture

在retain通过断点查看堆栈,可以看到font的png在创建后一共经过三次retain
通过csb创建的字体和手动create的字体没有区别,都是这三次

  • release

    • create代码创建

      • TextureAtlas::~TextureAtlas()

      • FontAtlas::releaseTextures()

    • csb创建

      • TextureAtlas::~TextureAtlas()

在release内断点的堆栈可以看出csb创建的font字体少了一个FontAtlas内释放Texture的过程


FontAtlas查找纹理释放

  • releaseTextures()
void FontAtlas::releaseTextures()
{
    for( auto &item: _atlasTextures)
    {
        item.second->getPath().c_str());
        item.second->release();
    }
    _atlasTextures.clear();
}

在fontatlas的releaseTextures内打断点,发现根本没有调用

  • FontAtlas析构函数
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 /* = Vec2::ZERO */)
{
    ......
    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的创建判断过程,在用的时候直接创建

推送

  • Github
https://github.com/KingSun5

结语

虽然问题解决了,但是不太清楚是不是当时写框架的人有别的考量的地方,如果有同学知道有博主没有考虑到的地方,欢迎留言交流,最后希望看到最后的同学有所收获,若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。
QQ交流群:806091680(Chinar)
该群为CSDN博主Chinar所创,推荐一下!我也在群里!
本文属于原创文章,转载请著名作者出处并置顶!!