Spark SkinnableComponent skinDestructionPolicy

2019-07-22 01:55发布

问题:

As a part of trying to tackle a memory leak in our application, we discovered that for every SkinnableComponent, the skinDestructionPolicy is set to "never" by default.

This means that when using static skin parts, the skin is forever detained in memory.
Furthermore, the override of a partRemoved() in the host component will never be triggered. Hence, event listeners we add in the partAdded() override are not removed, which effectively causes views and skins to be kept in memory.

When doing a lot of view switches this is just not acceptable.

Here is an example of of how we are working around this now:

public class ViewA extends SkinnableComponent
{
    [SkinPart(required = "true")]
    public var labelA:Label;

    [SkinPart(required = "true")]
    public var buttonA:Button;

    public function ViewA()
    {
        super();
        mx_internal::skinDestructionPolicy = 'auto';
    }

    override protected function getCurrentSkinState():String
    {
        return super.getCurrentSkinState();
    }

    override protected function partAdded(partName:String, instance:Object):void
    {
        super.partAdded(partName, instance);

        trace("ViewA::partAdded " + partName);

        if (instance == buttonA)
        {
            buttonA.addEventListener(MouseEvent.CLICK, buttonClickedHandler);
        }
    }

    override protected function partRemoved(partName:String, instance:Object):void
    {


        trace("ViewA::partRemoved " + partName);

        if (instance == buttonA)
        {
            buttonA.removeEventListener(MouseEvent.CLICK, buttonClickedHandler);
        }

        super.partRemoved(partName, instance);
    }

    override public function stylesInitialized():void
    {
        setStyle("skinClass", ViewASkin);
    }
}

However, using the mx::internal way to circumvent this behavior seems rather odd to me. Documentation about this is scarce as well, so any ideas will be very welcome.

Cheers

回答1:

In my experience the usage of the mx::internal namespace in the Flex SDK usually means something along the lines of: "you can use this functionality, if you know what you're doing, and also we (Adobe, or the Apache community in the future) don't guarantee that this API will never change in future versions of Flex".

So there's no real issue with its usage, unless you're very concerned with backwards compatibility. If you really want to avoid using it, you can always just implement the behavior of skinDestructionPolicy="auto" in your subclass. There's not that much code to write:

    override public function initialize():void {
        addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
        addEventListener(Event.REMOVED_FROM_STAGE, removedFromStageHandler);

        super.initialize();
    }

    private function addedToStageHandler(event:Event):void {
        if (skin == null) attachSkin();
    }

    private function removedFromStageHandler(event:Event):void {
        detachSkin();
    }

Note that in the SkinnableComponent class these two event listeners are attached (or not, depending on the policy) in the commitProperties() method. I moved that to the initialize() method because we don't need to check for changes in the skinDestructionPolicy property anymore.

Also note that this solution might cause an error if one did set mx::internal skinDestructionPolicy to "auto" alongside.