Search code examples
actionscript-3flashmemory-leaksair

AS3 drawing Sprite on BitmapData memory leak


I have an AIR app that renders images from a Flash editor. You can customize several surfaces - they all have the same width and height. Each surface is then rendered by the AIR app.

I'm struggling with a memory leak I fail to address.

For each surface I need to render, I have a Sprite containing many components (other sprites - some having event listeners, BitmapDatas and other children components, sprites, etc).

I'm already aware of BitmapData issues to be garbage collected, I tested both :

  • creating a new BitmapData for each rendering then dispose() and point to null
  • reuse a single BitmapData for each rendering

Memory leak is still happening in equal proportion.

Here is my loop :

var bm:BitmapData = new BitmapData(destDim.x, destDim.y, true, bgColor);
var mtx:Matrix = new Matrix();
trace('before drawing :'+(System.privateMemory/1024));
bm.draw(myBigSprite, mtx, null, null, null, true);
trace('after drawing :'+(System.privateMemory/1024));
var result:Bitmap = new Bitmap(bm, PixelSnapping.NEVER, true);

//return result and encode Bitmap to png

result.bitmapData.dispose();
result.bitmapData = null;
result = null;

Result :

before drawing :208364
after drawing :302816
Wrote bitmap to file: surface0.png
before drawing :303296 
after drawing :446160 
Wrote bitmap to file: surface1.png 
before drawing :446160
after drawing :565212
Wrote bitmap to file: surface2.png 
before drawing :565924
after drawing :703100 
Wrote bitmap to file: surface3.png 
before drawing :703572
after drawing :834420 
Wrote bitmap to file: surface4.png 

I feel like I'm missing something in draw function behaviour. It seems like I have newly created instances of the components of myBigSprite that persists after draw operation.

I tried to completely destroy myBigSprite at the end of each loop, it does not change anything....

Any hint would be appreciated !


Solution

  • Ok guys, I eventually understood and fixed this issue.

    First of all, I installed and ran Adobe Scout. Excellent tool.

    Adobe Scout memory profiling

    As you may not see (plus it's in French language), I generated 3 surfaces corresponding to the edges. The "big" green bar on the right which is mass memory consuming represent "Bitmap display objects". Interesting ! Never heard of those before.

    A Google search later, I found this article : https://help.adobe.com/en_US/as3/dev/WS5b3ccc516d4fbf351e63e3d118a9b90204-7e26.html

    It explains

    For example, in the code excerpt shown earlier, once the load operation for the pict Loader object is complete, the pict object will have one child display object, which is the bitmap, loaded. To access this bitmap display object, you can write pict.getChildAt(0).

    So I began to undestand that, somehow, maybe Bitmap object are attached as children on some objects of myBigSprite.

    Finally, I created a recursive function to search and destroy all Bitmap, BitmapData and ByteArray objects contained in myBigSprite AFTER the draw operation

    //inside render function
    bm.draw(myBigSprite, mtx, null, null, null, true);
    destroyDisplayObjects(myBigSprite);
    

    ...

    private function destroyDisplayObjects(obj):void{
        if ("numChildren" in obj){
            for (var i:int = 0; i<obj.numChildren; i++)
            {
                destroyDisplayObjects(obj.getChildAt(i));
            }
        }
        else {
            if (flash.utils.getQualifiedClassName(obj) == "flash.display::Bitmap"){
                //trace ('FREE BITMAP');
                obj.bitmapData.dispose();
                obj.bitmapData = null;
                obj = null;
                return;
            }
            else if (flash.utils.getQualifiedClassName(obj) == "flash.display::BitmapData"){
                //trace ('FREE BITMAPDATA');
                obj.dispose();
                obj = null;
                return;
            }
            else if (flash.utils.getQualifiedClassName(obj) == "flash.display::ByteArray"){
                //trace ('FREE BYTEARRAY');
                obj.clear();
                obj = null;
                return;
            }
    
            return;
        }
    }
    

    Et voilà, memory is 100% cleaned after the draw operation, no more leak :)