加载图像后创建内联函数会导致内存泄漏

时间:2015-10-19 09:32:56

标签: actionscript-3 flash memory-leaks image-loading adobe-scout

我正在分析游戏项目中意外的内存泄漏,并发现了一些奇怪的结果。我正在使用Adobe Scout进行分析,并消除了所有其他因素,如椋鸟,纹理或我们的加载库。我将代码简化为简单加载png并立即在其complete事件上分配一个空的内联函数。

加载png会在默认情况下分配图像,如果在加载gc后什么都不做,则会清除该图像。但是创建一个内联函数似乎可以防止该图像以某种方式被垃圾收集。我的测试代码是;

public class Main extends Sprite 
{
    private var _callbacks:Array = new Array();

    public function Main() 
    {
        load("map.png", onPngLoaded);
    }

    private function onPngLoaded(bitmap:Bitmap):void 
    {
        _callbacks.push(function():void { });
    }

    public function load(url:String, onLoaded:Function):void 
    {
        var loader:Loader = new Loader;

        var completeHandler:Function = function(e:Event):void {
            loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, completeHandler);
            onLoaded(loader.content);
        }

        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);

        loader.load(new URLRequest(url));   
    }
}

如果删除了创建内联函数的代码;

    private function onPngLoaded(bitmap:Bitmap):void 
    {
        // removed the code here!
    }

gc可以从内存中清除图像。

由于对此没有合理的解释,我怀疑是flash / as3错误。我很乐意听到任何测试我的代码并获得相同结果的评论。

注意:要测试,请用我的代码和导入包替换空as3项目的主类。你可以加载任何png。我正在使用flashdevelop,flex-sdk 4.6.0和flash player 14.

2 个答案:

答案 0 :(得分:2)

创建内联函数时,所有局部变量都会与全局范围一起存储。所以在这种情况下,那将包括bitmap参数。

有关详细信息,请参阅: http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f54.html

以下是相关部分:

  

每当函数开始执行时,都会创建许多对象和属性。首先,创建一个称为激活对象的特殊对象,它存储参数以及在函数体中声明的任何局部变量或函数。其次,创建一个范围链,其中包含Flash Player或Adobe AIR的有序对象列表检查标识符声明。每个执行的函数都有一个存储在内部属性中的作用域链。对于嵌套函数,作用域链以其自己的激活对象开始,后跟其父函数的激活对象。链以这种方式继续,直到它到达全局对象。

这是在大多数情况下最好避免使用内联/匿名函数的另一个原因。

答案 1 :(得分:1)

所以使用asc2,Flash / Air 19:是的,我得到的结果与你看到的相同,但是由于匿名函数持有全局引用,我预计(就像我原来的评论所述)。

我根据Adobe的GC技术文章和公告以我的风格重写了它,并且没有泄漏,因为所有全局引用都被删除了。

剪切/粘贴AIR示例:

package {

    import flash.events.MouseEvent;
    import flash.text.TextField;
    import flash.display.Sprite;
    import flash.display.Bitmap;
    import flash.display.Loader;
    import flash.events.Event;
    import flash.net.URLRequest;
    import flash.system.System;
    import flash.utils.Timer;
    import flash.events.TimerEvent;

    public class Main extends Sprite {
        var timer:Timer;
        var button:CustomSimpleButton;
        var currentMemory:TextField;
        var highMemory:TextField;
        var hi:Number;

        var _callbacks:Array = new Array();

        public function Main() {
            button = new CustomSimpleButton();
            button.addEventListener(MouseEvent.CLICK, onClickButton);
            addChild(button);
            currentMemory = new TextField();
            hi = System.privateMemory;
            currentMemory.text = "c: " + hi.toString();
            currentMemory.x = 100;
            addChild(currentMemory);
            highMemory = new TextField();
            highMemory.text = "h: " + hi.toString();
            highMemory.x = 200;
            addChild(highMemory);
            timer = new Timer(100, 1);
            timer.addEventListener(TimerEvent.TIMER_COMPLETE, timerHandler);
            timer.start();
        }

        function timerHandler(e:TimerEvent):void{
            System.pauseForGCIfCollectionImminent(.25);
            currentMemory.text = "c: " + System.privateMemory.toString();
            hi = System.privateMemory > hi ? System.privateMemory : hi;
            highMemory.text = "h: " + hi.toString();
            timer.start();
        }

        function onClickButton(event:MouseEvent):void {
            for (var i:uint = 0; i<100; i++) {
                //load("foobar.png", onPngLoaded);
                load2("foobar.png");
            }
        }

        private function onPngLoaded2(bitmap:Bitmap):void {
            var foobarBitMap:Bitmap = bitmap; // assuming you are doing something
            foobarBitMap.smoothing = false;   // with the bitmap...
            callBacks(); // not sure what you are actually doing with this
        }
        private function callBacks():void {
            _callbacks.push(function ():void {
            });
        }

        public function completeHandler2(e:Event):void {
            var target:Loader = e.currentTarget.loader as Loader;
            // create a new bitmap based what is in the loader so the loader has not refs after method exits
            var localBitmap:Bitmap = new Bitmap((target.content as Bitmap).bitmapData);
            onPngLoaded2(localBitmap);
        }

        public function load2(url:String):void {
            var loader2:Loader = new Loader;
            loader2.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler2, false, 0, true);
            loader2.load(new URLRequest(url));
        }
    }
}

import flash.display.Shape;
import flash.display.SimpleButton;

class CustomSimpleButton extends SimpleButton {
    private var upColor:uint   = 0xFFCC00;
    private var overColor:uint = 0xCCFF00;
    private var downColor:uint = 0x00CCFF;
    private var size:uint      = 80;

    public function CustomSimpleButton() {
        downState      = new ButtonDisplayState(downColor, size);
        overState      = new ButtonDisplayState(overColor, size);
        upState        = new ButtonDisplayState(upColor, size);
        hitTestState   = new ButtonDisplayState(upColor, size * 2);
        hitTestState.x = -(size / 4);
        hitTestState.y = hitTestState.x;
        useHandCursor  = true;
    }
}

class ButtonDisplayState extends Shape {
    private var bgColor:uint;
    private var size:uint;

    public function ButtonDisplayState(bgColor:uint, size:uint) {
        this.bgColor = bgColor;
        this.size    = size;
        draw();
    }

    private function draw():void {
        graphics.beginFill(bgColor);
        graphics.drawRect(0, 0, size, size);
        graphics.endFill();
    }
}