[√]Android平台ParticleSystem内存泄露的排查过程

简介: [√]Android平台ParticleSystem内存泄露的排查过程

测试代码/问题表现

local node = cc.ParticleSystemQuad:create(filename)
local size = cc.Director:getInstance():getWinSize()
node:setPosition(cc.p(size.width / 2, size.height / 2))
scene:addChild(node)

每创建一个粒子,就会发生一次泄露,使用leak-tracer检测内存泄露,指向的位置为Image.cpp,起初我以为是误报,后来经过验证,也就是将image的malloc地址缓存起来,image析构的时候再移除,最后观察是否还有image没有移除,确认就是image造成的内存泄露。

问题定位

只有在Android平台,才启用了CC_ENABLE_CACHE_TEXTURE_DATA,看到网上有说这个是为了安卓切换到后台再返回的时候,避免因为重新加载纹理导致的黑屏问题。

c++

复制代码

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) 
    #define CC_ENABLE_CACHE_TEXTURE_DATA       1
#else
    #define CC_ENABLE_CACHE_TEXTURE_DATA       0
#endif

在CCParticleSystem中,解析plist并加载设置纹理

 if( dictionary.find("textureImageData") != dictionary.end() ){
     // ...
     // For android, we should retain it in VolatileTexture::addImage which invoked in Director::getInstance()->getTextureCache()->addUIImage()
    image = new (std::nothrow) Image(); //image.ref=1  就是这个new导致的泄露
    bool isOK = image->initWithImageData(deflated, deflatedLen);
    CCASSERT(isOK, "CCParticleSystem: error init image with Data");
    CC_BREAK_IF(!isOK);
    // texture.ref=1,CC_ENABLE_CACHE_TEXTURE_DATA的原因,image.ref=2
    auto texture = Director::getInstance()->getTextureCache()->addImage(image, _plistFile + textureName);
    setTexture(texture);// texture.ref=2
    image->release();// image.ref=2 无法释放
}

注释中也说明了,在Android平台,Image会额外的retain一次,所以这个的image->release()是无法释放的,乍一看应该是这个image的问题,其实不然,解析往后看。

image->release()无法释放的原因,CCTextureCache.cpp中相关代码:

Texture2D* TextureCache::addImage(Image* image, const std::string& key){
    // ...
    #if CC_ENABLE_CACHE_TEXTURE_DATA
        VolatileTextureMgr::addImage(texture, image); // 重点逻辑
    #endif
}
std::list<VolatileTexture*> VolatileTextureMgr::_textures;
void VolatileTextureMgr::addImage(Texture2D* tt, Image* image)
{
    if (tt == nullptr || image == nullptr)
        return;
    // 相当于Texture2D和VolatileTexture是一一映射的关系
    VolatileTexture* vt = findVolotileTexture(tt);
    image->retain(); // retain
    // 同一个plist,第二次创建时texture都是同一个,因为都是从TextureCache中获取的,所以上边的vt也是同一个
    // 但是image发生了变化,直接赋值就让之前的image变成了野指针
    // CC_SAFE_RELEASE_NULL(vt->_uiImage); // 修复内存泄露的代码
    vt->_uiImage = image;
    vt->_cashedImageType = VolatileTexture::kImage;
}
VolatileTexture* VolatileTextureMgr::findVolotileTexture(Texture2D* tt)
{
    VolatileTexture* vt = nullptr;
    for (const auto& texture : _textures)
    {
        VolatileTexture* v = texture;
        if (v->_texture == tt)
        {
            vt = v;
            break;
        }
    }
    if (!vt)
    {
        vt = new (std::nothrow) VolatileTexture(tt);
        _textures.push_back(vt);
    }
    return vt;
}
VolatileTexture::VolatileTexture(Texture2D* t)
    : _texture(t) // 对应的纹理
{
}

粒子销毁时,析构函数的逻辑,因为粒子对应的texture在TextureCache中有一份,所以粒子的texture在游戏过程中永远也无法释放,除非手动清理TextureCache,

ParticleSystem::~ParticleSystem()
{
    _particleData.release();
    CC_SAFE_RELEASE(_texture);// texture.ref=2,texture无法释放
}
Texture2D::~Texture2D()
{
#if CC_ENABLE_CACHE_TEXTURE_DATA
    VolatileTextureMgr::removeTexture(this);// 导致vt绑定的_uiImage也无法释放
#endif
}
void VolatileTextureMgr::removeTexture(Texture2D* t)
{
    for (auto& item : _textures)
    {
        VolatileTexture* vt = item;
        if (vt->_texture == t)// 对应的纹理
        {
            _textures.remove(vt);
            delete vt;
            break;
        }
    }
}
VolatileTexture::~VolatileTexture()
{
    CC_SAFE_RELEASE(_uiImage);
}

整理下思路: TextureCache::addImage的逻辑中存在一条映射链

image / particle.texture / VolatileTexture._texture / VolatileTexture._uiImage

按照这个逻辑推理,如果image想要释放那么只需要particle.texture释放即可,很明显particle.texture释放不了,原因我上边的注释也写了,因为TextureCache的原因,注释我已经写的很清楚了,可以回过头再仔细理解下。

所以,问题就变成了我们不能指望着particle.texture释放,它也释放不了,所以保存image指针前,就需要看texture绑定的是否有_uiImage,有的话,就把之前的释放掉,这样简单粗暴的就修复了内存泄露的问题。

扩展

重复创建同一个粒子,你会发现其实这个image的内容没有发生变化,还有一种修复办法就是CC_ENABLE_CACHE_TEXTURE_DATA模式下把image缓存起来,粒子纹理从image cache中取,还能提升一定的性能,可扩展的修复结果:

void VolatileTextureMgr::addImage(Texture2D* tt, Image* image)
{
    if (tt == nullptr || image == nullptr)
        return;
    VolatileTexture* vt = findVolotileTexture(tt);
    image->retain();
    // release previous image, otherwise case memory leak
    if (vt->_uiImage != image){ //万一image是从cache里面取的呢?
        CC_SAFE_RELEASE_NULL(vt->_uiImage);
    }
    vt->_uiImage = image;
    vt->_cashedImageType = VolatileTexture::kImage;
}

Sprite为啥没有这个问题呢?

Texture2D* addImage(Image *image, const std::string &key);// 有vt
Texture2D* addImage(const std::string &filepath, bool bSpriteFrame);// sprite调用的是这个,没有vt
目录
相关文章
|
21天前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
79 1
|
23天前
|
存储 前端开发 Java
Android MVVM架构模式下如何避免内存泄漏
Android采用MVVM架构开发项目,如何避免内存泄漏风险?怎样避免内存泄漏?
73 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
12天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
49 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
14天前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
35 2
|
2月前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
66 6
|
2月前
|
监控 Java Linux
redisson内存泄漏问题排查
【9月更文挑战第22天】在排查 Redisson 内存泄漏问题时,首先需确认内存泄漏的存在,使用专业工具(如 JProfiler)分析内存使用情况,检查对象实例数量及引用关系。其次,检查 Redisson 使用方式,确保正确释放资源、避免长时间持有引用、检查订阅和监听器。此外,还需检查应用程序其他部分是否存在内存泄漏源或循环引用等问题,并考虑更新 Redisson 到最新版本以修复潜在问题。
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发环境对比:选择适合你的平台
【9月更文挑战第26天】在移动应用开发的广阔天地中,安卓和iOS是两大巨头。它们各自拥有独特的优势和挑战,影响着开发者的选择和决策。本文将深入探讨这两个平台的开发环境,帮助你理解它们的核心差异,并指导你根据个人或项目需求做出明智的选择。无论你是初学者还是资深开发者,了解这些平台的异同都至关重要。让我们一起探索,找到最适合你的那片开发天地。
|
2月前
|
Android开发 开发者
Android平台无纸化同屏如何实现实时录像功能
Android平台无纸化同屏,如果需要本地录像的话,实现难度不大,只要复用之前开发的录像模块的就可以,对我们来说,同屏采集这块,只是数据源不同而已,如果是自采集的其他数据,我们一样可以编码录像。
|
2月前
|
安全 API 开发工具
Android平台RTMP推送|轻量级RTSP服务如何实现麦克风|扬声器声音采集切换
Android平台扬声器播放声音的采集,在无纸化同屏等场景下,意义很大,早期低版本的Android设备,是没法直接采集扬声器audio的(从Android 10开始支持),所以,如果需要采集扬声器audio,需要先做系统版本判断,添加相应的权限。