[√]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
目录
相关文章
|
1月前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
1月前
|
存储 弹性计算 数据中心
倚天产品介绍|倚天710平台稳定性-内存隔离降级运行
本文介绍利用倚天710平台的RAS特性,实现OS降级运行,提高系统稳定性
|
2月前
|
Android开发
安卓SO层开发 -- 编译指定平台的SO文件
安卓SO层开发 -- 编译指定平台的SO文件
33 0
|
30天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
82 0
|
10天前
|
移动开发 Android开发 开发者
构建高效Android应用:采用Kotlin进行内存优化的策略
【4月更文挑战第18天】 在移动开发领域,性能优化一直是开发者关注的焦点。特别是对于Android应用而言,由于设备和版本的多样性,确保应用流畅运行且占用资源少是一大挑战。本文将探讨使用Kotlin语言开发Android应用时,如何通过内存优化来提升应用性能。我们将从减少不必要的对象创建、合理使用数据结构、避免内存泄漏等方面入手,提供实用的代码示例和最佳实践,帮助开发者构建更加高效的Android应用。
|
12天前
|
缓存 移动开发 Java
构建高效的Android应用:内存优化策略
【4月更文挑战第16天】 在移动开发领域,尤其是针对资源有限的Android设备,内存优化是提升应用性能和用户体验的关键因素。本文将深入探讨Android应用的内存管理机制,分析常见的内存泄漏问题,并提出一系列实用的内存优化技巧。通过这些策略的实施,开发者可以显著减少应用的内存占用,避免不必要的后台服务,以及提高垃圾回收效率,从而延长设备的电池寿命并确保应用的流畅运行。
|
30天前
|
监控 网络协议 NoSQL
java线上排查OOM内存溢出
java线上排查OOM内存溢出
25 0
|
1月前
|
缓存 移动开发 Java
构建高效Android应用:内存优化实战指南
在移动开发领域,性能优化是提升用户体验的关键因素之一。特别是对于Android应用而言,由于设备和版本的多样性,内存管理成为开发者面临的一大挑战。本文将深入探讨Android内存优化的策略和技术,包括内存泄漏的诊断与解决、合理的数据结构选择、以及有效的资源释放机制。通过实际案例分析,我们旨在为开发者提供一套实用的内存优化工具和方法,以构建更加流畅和高效的Android应用。
|
1月前
|
运维 监控 Java
应用研发平台EMAS产品常见问题之安卓构建版本失败如何解决
应用研发平台EMAS(Enterprise Mobile Application Service)是阿里云提供的一个全栈移动应用开发平台,集成了应用开发、测试、部署、监控和运营服务;本合集旨在总结EMAS产品在应用开发和运维过程中的常见问题及解决方案,助力开发者和企业高效解决技术难题,加速移动应用的上线和稳定运行。
|
1月前
|
监控 Java Android开发
构建高效Android应用:从内存管理到性能优化
【2月更文挑战第30天】 在移动开发领域,打造一个流畅且响应迅速的Android应用是每个开发者追求的目标。本文将深入探讨如何通过有效的内存管理和细致的性能调优来提升应用效率。我们将从分析内存泄露的根本原因出发,讨论垃圾回收机制,并探索多种内存优化策略。接着,文中将介绍多线程编程的最佳实践和UI渲染的关键技巧。最后,我们将通过一系列实用的性能测试工具和方法,帮助开发者监控、定位并解决性能瓶颈。这些技术的综合运用,将指导读者构建出更快速、更稳定、用户体验更佳的Android应用。

热门文章

最新文章