问题原因
当存在spine动画时,调用cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()可能会引起内存泄漏。
源码分析
当创建一个Spine动画并把它在场景中展示出来时,每一帧都会调用SkeletonRenderer::draw()
在draw()中会获取SkeletonBatch和SkeletonTwoColorBatch的单例,并调用它们的addCommand()、allocateVertices()、allocateIndices()
void SkeletonRenderer::draw (Renderer* renderer, const Mat4& transform, uint32_t transformFlags) {
......
......
const float* worldCoordPtr = worldCoords;
SkeletonBatch* batch = SkeletonBatch::getInstance();
SkeletonTwoColorBatch* twoColorBatch = SkeletonTwoColorBatch::getInstance();
const bool hasSingleTint = (isTwoColorTint() == false);
......
......
for (int i = 0, n = _skeleton->getSlots().size(); i < n; ++i) {
......
......
if (hasSingleTint) {
if (_clipper->isClipping()) {
......
......
#if COCOS2D_VERSION < 0x00040000
ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
#else
ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
#endif
......
......
} else {
// Not clipping.
#if COCOS2D_VERSION < 0x00040000
ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _glProgramState, blendFunc, triangles, transform, transformFlags);
#else
ax::TrianglesCommand* batchedTriangles = batch->addCommand(renderer, _globalZOrder, attachmentVertices->_texture, _programState, blendFunc, triangles, transform, transformFlags);
#endif
......
......
}
} else {
......
......
}
_clipper->clipEnd(*slot);
}
......
......
}
我们以SkeletonBatch的代码进行分析(SkeletonTwoColorBatch情况相同):
- 在SkeletonBatch的构造函数中,先初始化了10000个command放到_commandsPool中。然后监听eventDispatcher发送EVENT_AFTER_DRAW_RESET_POSITIO(director_after_draw)事件,去执行update(),对_nextFreeCommand、_numVertices、_indices进行重置。
- 在addCommand()中会调用nextFreeCommand(),在nextFreeCommand()中,如果_nextFreeCommand>=_commandsPool.size(),对_commandsPool进行2倍扩容, _nextFreeCommand加1。
- 同理在allocateVertices()、allocateIndices()中如果_numVertices、_indices过大也会对_vertices和_indices进行2倍扩容,并增加_numVertices、_indices。
spine动画的正常逻辑:是在每一帧draw的时候处理当前帧的批处理命令(这个过程中会增加_nextFreeCommand、_numVertices、_indices),然后再通过监听director_after_draw事件重置_nextFreeCommand、_numVertices、_indices。
但是cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()方法会清空所有监听director_after_draw事件的方法,导致SkeletonBatch无法重置_nextFreeCommand、_numVertices、_indices。随着每一帧draw()的调用,_commandsPool、_vertices、_indicesd的容量会越来越大。导致内存泄漏。
USING_NS_AX;
#define EVENT_AFTER_DRAW_RESET_POSITION "director_after_draw"
using std::max;
#define INITIAL_SIZE (10000)
......
......
SkeletonBatch::SkeletonBatch () {
auto program = backend::Program::getBuiltinProgram(backend::ProgramType::POSITION_TEXTURE_COLOR);
_programState = new backend::ProgramState(program); // new default program state
for (unsigned int i = 0; i < INITIAL_SIZE; i++) {
_commandsPool.push_back(newCommand());
}
reset();
// callback after drawing is finished so we can clear out the batch state
// for the next frame
Director::getInstance()->getEventDispatcher()->addCustomEventListener(EVENT_AFTER_DRAW_RESET_POSITION, [this](EventCustom* eventCustom) {
this->update(0);
});;
}
SkeletonBatch::~SkeletonBatch () {
Director::getInstance()->getEventDispatcher()->removeCustomEventListeners(EVENT_AFTER_DRAW_RESET_POSITION);
for (unsigned int i = 0; i < _commandsPool.size(); i++) {
AX_SAFE_RELEASE(_commandsPool[i]->getPipelineDescriptor().programState);
delete _commandsPool[i];
_commandsPool[i] = nullptr;
}
AX_SAFE_RELEASE(_programState);
}
void SkeletonBatch::update (float delta) {
reset();
}
ax::TrianglesCommand* SkeletonBatch::addCommand(ax::Renderer* renderer, float globalOrder, ax::Texture2D* texture, backend::ProgramState* programState, ax::BlendFunc blendType, const ax::TrianglesCommand::Triangles& triangles, const ax::Mat4& mv, uint32_t flags) {
SkeletonCommand* command = nextFreeCommand();
const ax::Mat4& projectionMat = Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
if (programState == nullptr)
programState = _programState;
AXASSERT(programState, "programState should not be null");
auto pipelinePS = updateCommandPipelinePS(command, programState);
pipelinePS->setUniform(command->_locMVP, projectionMat.m, sizeof(projectionMat.m));
pipelinePS->setTexture(command->_locTexture, 0, texture->getBackendTexture());
command->init(globalOrder, texture, blendType, triangles, mv, flags);
renderer->addCommand(command);
return command;
}
void SkeletonBatch::reset() {
_nextFreeCommand = 0;
_numVertices = 0;
_indices.setSize(0, 0);
}
SkeletonCommand* SkeletonBatch::nextFreeCommand() {
if (_commandsPool.size() <= _nextFreeCommand) {
unsigned int newSize = _commandsPool.size() * 2 + 1;
for (int i = _commandsPool.size(); i < newSize; i++) {
_commandsPool.push_back(newCommand());
}
}
auto* command = _commandsPool[_nextFreeCommand++];
return command;
}
SkeletonCommand* SkeletonBatch::newCommand() {
auto* command = new SkeletonCommand();
return command;
}
建议
- 尽量不要同时使用Spine动画和cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()
- 调用cc.Director:getInstance():getEventDispatcher():removeAllEventListeners()后,恢复SkeletonBatch和SkeletonTwoColorBatch监听director_after_draw事件。
- 自己实现一个清除监听的方法,对Spine做特殊处理,不要清除spine的监听director_after_draw事件。