Архив рубрики Flex

Ошибка «Channel disconnected»

Ошибка «Channel disconnected before an acknowledgement was received» может появлятся по разным причинам. Часть из них связана с php (например когда вместо AMF отдается текст), часть с ошибками сериализации. Для ее отлова полезно использовать либо firebug, либо charlesproxy.

В моем случае использовалась связка Tomcat+BlazeDS. Долго неудавалось повторить ошибку, но все же получилось, вот что оказалось в логах firebug:

Responce headers

Ответ полученный клиентом

Как видно из скриншота, прокси сервер разорвал соединение, клиент получил 0 байт ответа и отвалился с этой загадочной ошибкой. Судя по хидерам X-Cache, используется цепочка прокси, буду благодарен за любую информацию по этой проблеме.

Реклама

, , , , , , ,

Оставить комментарий

Наступаем на грабли IDropInListItemRenderer

Иногда бывает необходимость получить доступ из рендерера к листу, в котором этот рендерер находится. Для этого необходимо заиплементить интерфейс IDropInListItemRenderer и затем взять поле owner у полученной на руки BaseListData. Это все будет прекрасно работать до тех пор, пока лист не окажется пустым:

        if (item is IDropInListItemRenderer)
        {
            if (data != null)
                IDropInListItemRenderer(item).listData = makeListData(data, itemToUID(wrappedData), 0);
            else
                IDropInListItemRenderer(item).listData = null;
        }

Поэтому использовать интерфейс IDropInListItemRenderer необходимо с осторожностью, либо передавать ссылку на лист через конструктор.

, ,

Оставить комментарий

Как преобразовать цвет из hex-строки("0xFF0000") в uint?

Ответ простой:

var color:uint =  uint(«0xff0000»);

, , , ,

Оставить комментарий

Как отключить механизм callLater

Основной проблемой при разрушении объектов вручную становится метод callLater. Даже если объект удален из дисплейлиста элементы жизненного цикла компонентов (validate/invalidate/update) продолжают работать, а они в свою очередь активно пользуются механизмом callLater. Это не позволяет обнулять некоторые ссылки, которые используются в методах добавленных для выполнения с помощью callLater. Отключить его, как оказалось, легко — необходимо написать декоратор для SystemManager (имплементация ISystemManager) и перекрыть в нем метод addEventListener, отключив добавление слушателя для событий типа FlexEvent.RENDER и FlexEvent.ENTER_FRAME. Подменив мененджер на свой необходимо также вызвать component.mx_internal::cancelAllCallLaters(), чтобы удалить ранее добавленные методы. Вуаля!

,

Оставить комментарий

Label: truncateToFit — проблемы с производительностью

Неожиданно открывшийся тикет поставил в тупик: дерево отображающее XMP начинало тормозить без видимой причины при раскрытии некоторых нодов. Как оказалось, это были ноды содержащие кодированые в base64 изображения(достаточно большой текст). Казалось бы что тут такого, xml небольшой, значит датадескриптор тормозить не может, видимого текста мало, компонентов в рендерере немного, почему все так плохо? Оказалось проблема скоро пойдет в школу. Если лень перейти по ссылке, быстрый ответ —  truncateToFit, развернутый:

while (s.length > 1 && textWidth + TEXT_WIDTH_PADDING > w) {
    s = s.slice(0, -1);
    super.text = s + truncationIndicator;
}

, ,

1 комментарий

Рисование стрелок во Flex и Flash

Шагая по гуглу нашел весьма интересный и полезный утилитный класс для рисования всевозможных стрелок GraphicsUtil. Прилагаю скриншот:

GraphicsUtil

GraphicsUtil

демо

, ,

Оставить комментарий

Как передать дополнительные параметры в слушатель (addEventListener передать переменную)

Многим приходилось сталкиваться с необходимостью передать дополнительные параметры в слушатель (listener). Мне это понадобилось для реализации ленивой загрузки(lazy loading) в дереве.

Как это работает:

  • пользователь нажимает на крестик
  • отправляется запрос на сервер (подгрузка нодов)
  • отображение загруженных нодов

Таким образом, возникает необходимость хранить где-то дополнительную информацию — куда вставить загруженные ноды? Есть конечно возможность выкрутиться: заполнить на сервере в нодах, например, parentID, затем искать по дереву нужный нод с id=parentID и в него уже ложить загруженные ноды. С точки зрения производительности не лучший вариант. А можно написать вспомогательный класс:

package
{
    import mx.collections.ArrayCollection;
    import mx.controls.Tree;
    import mx.rpc.events.ResultEvent;

    public class NodeLoader
    {
        private var tree:Tree;
        private var parentNode:Object;

        /**
         * Collection of NodeLoaders
         */
        private var listeners:ArrayCollection;

        public function NodeLoader(tree:Tree, parentNode:Object, listeners:ArrayCollection)
        {
            this.listeners = listeners;
            this.parentNode = parentNode;
            this.tree = tree;
        }

        public function onSuccess(event:ResultEvent):void
        {
            var nodes:ArrayCollection = ArrayCollection(event.result);
            //todo add nodes ... whatever

            clean();
        }

        public function clean():void
        {
            tree = null;
            parentNode = null;
            listeners.removeItemAt(listeners.getItemIndex(this));
            //todo clean any other references
        }
    }
}

Для использования этого класса необходимо лишь одно дополнительное поле — loaders:ArrayCollection, это нужно для того, чтобы использовать слабые ссылки при добавлении слушателей. Добавив NodeLoader в коллекцию мы предотвратим возможную сборку мусора. Разумеется, можно было бы обойтись без дополнительного поля, используя сильные ссылки и передавая в NodeLoader RemoteObject, чтобы была возможность отписаться от события, все зависит от вас.

Пример:

var loader:NodeLoader = new NodeLoader(tree, parentNode, loaders);
loaders.addItem(loader);
remote["load"].addEventListener(ResultEvent.RESULT, loader.onSuccess, false, 0, true);

Вдохновение от flasher.ru

Еще проще:


    public class Context
    {
        private var f:Function;
        private var context:*;
        private var args:Array;

        public function Context( context:*, f:Function, ...args)
        {
            this.f = f;
            this.context = context;
            this.args = args;
        }

        public function onSomething(...rest):void
        {
            f.apply(context, rest.concat(args));
            f = null;
            context = null;
            args = null;
        }
    }

    facade.getCategories(new Context(this, onCategoriesLoaded, new FilterValueVO()).onSomething, onFault);

    private function onCategoriesLoaded(event:ResultEvent, val:FilterValueVO):void
    {
    }

, , ,

Оставить комментарий

Базовая очистка базовых компонентов ч.1

Мусор

Фреймворк, по крайней мере Flex 3 SDK, написан так, что при всем правильном использовании и следовании flex-way, память будет расти как снежный ком. Проблема в том, что сборщик мусора слишком ленив (либо не все в порядке в датском королевстве), поэтому он откладывает сложные конструкции (например с циклическими ссылками) на потом. Я написал несколько классов, которые позволяют «очищать» компоненты которые больше не используются, оптимально подготавливая их к сборке мусора.

Для удобства заведем интерфейс IComponentCleaner — для каждого типа компонентов заимплементим свою реализацию.

package 
{
    public interface IComponentCleaner
    {
        /**
         * Cleans component: removes hidden references
         * @param object
         */
        function clean(object:Object, stage:CleanStage):void

        /**
         * Returns true if component can be cleaned.
         * @return true if component can be cleaned.
         */
        function probe(object:Object, stage:CleanStage):Boolean;
    }
}

Класс CleanStage я опишу позже, он используется для указания, какие ссылки можно очистить на данный момент, это связано с иерархией дисплейлиста и неотключаемым методом callLater (основной источник исключений, которые к тому же нельзя перехватить). В моем случае всего 3 варианта:
CLEAN_INIT — перед разрушением иерархии объектов, CLEAN_INDEPTH_INIT — после того как был построен список вложенных дисплей-объектов и CLEAN_COMPLETE — окончательная очистка.

package 
{
    public class CleanStage
    {
        public static const CLEAN_INIT:CleanStage = new CleanStage(0);
        public static const CLEAN_INDEPTH_INIT:CleanStage = new CleanStage(2);
        public static const CLEAN_COMPLETE:CleanStage = new CleanStage(3);

        public var stage:int;

        public function CleanStage(stage:int)
        {
            this.stage = stage;
        }
    }
}

Далее код класса ComponentCleaner, который объединяет внутри себя разные имплементации IComponentCleaner.

package
{

    import flash.utils.Dictionary;
    import flash.utils.getQualifiedClassName;

    import mx.utils.UIDUtil;

    public class ComponentCleaner
    {
        private static var _instance:ComponentCleaner;

        private var cleaners:Dictionary = new Dictionary(false);

        public function ComponentCleaner(seal:Seal)
        {
            if (!seal)
            {
                throw new Error("private");
            }
        }

        public static function get instance():ComponentCleaner
        {
            if (!_instance)
            {
                _instance = new ComponentCleaner(new Seal);

                //this order must not be changed! most problems come with callLater
                _instance.registerCleaner(new BoxCleaner());
                _instance.registerCleaner(new ScrollControllCleaner2());
                _instance.registerCleaner(new TreeCleaner());
                _instance.registerCleaner(new DisposableCleaner());
                _instance.registerCleaner(new ContainerCleaner());
                _instance.registerCleaner(new ViewStackCleaner());
                _instance.registerCleaner(new CanvasCleaner());
                _instance.registerCleaner(new ButtonCleaner());
                _instance.registerCleaner(new UIComponentCleaner());
                _instance.registerCleaner(new StyleCleaner());
                _instance.registerCleaner(new BindingsCleaner());
                _instance.registerCleaner(new LoaderCleaner());
                _instance.registerCleaner(new BitmapDataCleaner());
                _instance.registerCleaner(new SWFLoaderCleaner());
            }
            return _instance;
        }

        public function registerCleaner(cleaner:IComponentCleaner):void
        {
            if (!cleaner)
            {
                throw new Error("cleaner is null");
            }

            var cleanerUID:String = UIDUtil.createUID();
            cleaners[cleanerUID] = cleaner;
        }

        public function probe(object:Object, stage:CleanStage):Array
        {
            var availableCleaners:Array = [];
            for each (var componentCleaner:IComponentCleaner in cleaners)
            {
                if (componentCleaner.probe(object, stage))
                {
                    availableCleaners.push(componentCleaner);
                }
            }
            return availableCleaners;
        }

        public function clean(object:Object, stage:CleanStage):void
        {
            var availableCleaners:Array = probe(object, stage);
            for each (var componentCleaner:IComponentCleaner in availableCleaners)
            {
                componentCleaner.clean(object, stage);
            }
        }
    }
}

class Seal
{

}

Как видно, будут использованы 14 разных имплементаций.

Для устранения большинства проблем хватит нескольких строк кода. Первый в списке — BoxCleaner.

package
{
    import mx.containers.Box;
    import mx.core.mx_internal;

    public class BoxCleaner implements IComponentCleaner
    {
        public function clean(object:Object, stage:CleanStage):void
        {
            var box:Box = Box(object);
            if(box.mx_internal::layoutObject)
                box.mx_internal::layoutObject.target = null;
            box.mx_internal::contentPane = null;
            box.mx_internal::layoutObject = new BoxLayoutWorkAround();
        }

        public function probe(object:Object, stage:CleanStage):Boolean
        {
            return object is Box && stage == CleanStage.CLEAN_INIT;
        }
    }
}

Вспомогательный класс, очистки ссылок которые хранит BoxLayout. BoxLayoutWorkAround необходим для предотвращения исключений во время выполнения callLater (просто установить null в layoutObject недостаточно). Используется IDisposable интерфейс, описанный в предыдущих постах.

package 
{
    import mx.containers.utilityClasses.BoxLayout;
    import mx.core.Container;

    public class BoxLayoutWorkAround extends BoxLayout implements IDisposable
    {
        private var disposed:Boolean;

        public function BoxLayoutWorkAround()
        {
            target = new Container();
        }

        public override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            //do nothing
        }

        public override function measure():void
        {
            //do nothing
        }

        public function dispose():void
        {
            this.target = null;
            disposed = true;
        }

        public function isDisposed():Boolean
        {
            return disposed;
        }
    }
}

Большинство ссылок лежит в mx_internal неймспейсе, но в случае с биндингом вообще в приватных полях.

Продолжение следует…

, ,

Оставить комментарий

Имплементация WeakReference

Часто возникает необходимость хранить ссылку на объект, не препятствуя при этом сборке этого объекта сборщиком мусора. Для этого уже есть примеры, но тем не менее :). Рассмотрим сначала самый важный класс — WeakReferenceHolder (непосредственно имплементация WeakReference):

package
{
    import flash.utils.Dictionary;

    public class WeakReferenceHolder
    {
        private var profiledObject:ProfiledObject; //stores object reference info
        private var ref:Dictionary = new Dictionary(true);//weak keys enabled

        public function WeakReferenceHolder(reference:Object = null, description:String = null)
        {
            if (reference != null)
            {
                profiledObject = ProfileObjectFactory.instance.create(reference, description);
                ref[reference] = profiledObject;
                //later i will publish sources for custom memory profiler
                //see https://compile4fun.wordpress.com/2010/10/31/memory-profile/
                //GCWatcher.instance.watch(reference, description ? description : "");
            }
        }

        public function set(reference:Object, description:String = null):void
        {
            clear();
            if (reference != null)
            {
                profiledObject = ProfileObjectFactory.instance.create(reference, description ? description : "");
                ref[reference] = profiledObject;
                //later i will publish sources for custom memory profiler
                //see https://compile4fun.wordpress.com/2010/10/31/memory-profile/
                //GCWatcher.instance.watch(reference, description ? description : "");
            }
        }

        public function clear():void
        {
            if (this.reference)
            {
                for (var reference:Object in ref)
                {
                    delete ref[reference];
                }
                trace("Free reference " + profiledObject.toString());
                profiledObject = null;
            }
        }

        public function get():Object
        {
            if(reference)
            {
                return reference;
            }
            else if(profiledObject != null)
            {
                trace("GC collected: " + profiledObject.toString());
                profiledObject = null;
            }
            return null;
        }

        private function get reference():Object
        {
            var result:Object;
            for (var reference:Object in ref)
            {
                result = reference;
            }
            return result;
        }
    }
}

Идея вобщем-то довольна проста: использовать встроенную возможность flash.utils.Dictionary — weakKeys («If the only reference to an object is in the specified Dictionary object, the key is eligible for garbage collection and is removed from the table when the object is collected.»). Дополнительная возможность — логирование информации об объекте, а также интеграция(в будущем) с профайлером памяти). Для этого понадобятся два дополнительных класса: ProfiledObject — value-объект, и ProfileObjectFactory — конструктор объектов типа ProfiledObject.
ProfiledObject:

package
{
    public class ProfiledObject
    {
        public var name:String;
        public var size:Number;
        public var currentSize:Number;
        public var watchStack:Object;
        public var clazz:String;
        public var superClazz:String;
        public var date:String;

        public var description:Object;

        public function ProfiledObject(name:String, size:Number, allocationStack:Object, clazz:String, superClazz:String)
        {
            this.name = name;
            this.size = size;
            this.watchStack = allocationStack;
            this.clazz = clazz;
            this.superClazz = superClazz;
        }

        public function toString():String
        {
            return String(date) + "\t" + String(clazz) + "\t" + String(clazz) + "\t"  + String(description) + "\t" + String(size);
        }
    }
}

ProfileObjectFactory:

package
{
    import flash.sampler.getSize;
    import flash.utils.getQualifiedClassName;
    import flash.utils.getQualifiedSuperclassName;

    import mx.collections.ArrayCollection;
    import mx.formatters.DateFormatter;
    import mx.utils.UIDUtil;

    public class ProfileObjectFactory
    {
        private static var dateFormatter:DateFormatter = new DateFormatter();

        private static var _instance:ProfileObjectFactory;

        public function ProfileObjectFactory()
        {
        }

        public static function get instance():ProfileObjectFactory
        {
            if (!_instance)
            {
                dateFormatter.formatString = "JJ:NN:SS";
                _instance = new ProfileObjectFactory();
            }
            return _instance;
        }

        public function create(obj:Object, description:String = ""):ProfiledObject
        {
            var profiledObject:ProfiledObject = new ProfiledObject(null, 0, getStack(), null, null);
            if (obj != null)
            {
                profiledObject.name = UIDUtil.createUID();
                profiledObject.description = (description && description.length > 0 ? description : "") + " " + obj.toString();
                profiledObject.size = getSize(obj);
                profiledObject.clazz = getQualifiedClassName(obj);
                profiledObject.superClazz = getQualifiedSuperclassName(obj);
                var date:Date = new Date();
                profiledObject.date = dateFormatter.format(date) + "." + date.getMilliseconds();
                return profiledObject;
            }
            else
            {
                return null;
            }
        }

        private function getStack():Object
        {
            var result:ArrayCollection = new ArrayCollection();
            try
            {
                throw new Error("Allocation trace:");
            }
            catch(e:Error)
            {
                var stack:String = e.getStackTrace();
                if (stack != null && stack.length > 0)
                {
                    result = new ArrayCollection(escape(stack).split("\n"));
                }
            }

            var obj:Object = {};
            var k:int = 1;
            for (var i:int = 0; i  3)
                {
                    obj[addLeadingZero(k)] = result.getItemAt(i);
                    k++;
                }
            }
            return obj;
        }

        public function addLeadingZero(n:Number):String
        {
            var out:String = String(n);

            if (n > -1 && n < 10)
            {
                out = "0" + out;
            }

            return out;
        }

        private function escape(str:String):String
        {
            var result:String = "";
            var instancePattern1:RegExp = /(\\)/gmi;
            result = str.replace(instancePattern1, "/");
            return result;
        }
    }
}

Использовать можно примерно так:

var control:WeakReferenceHolder = new WeakReferenceHolder(object, object ? "ServiceFrontend: " + object.toString() : null);
trace(control.get());

Пока времени больше нет, но материала еще много, продолжим тему чуть позже.

, , ,

2 комментария

Профайлеры: Flex Builder 3 vs Fash Builder 4

Adobe Flash Builder 4До недавнего времени я пользовался только профайлером из Flex Builder 3. Для большого приложения ему нехватает производительности, приходиться тратить много времени, чтобы прокликать нужный функционал.

И вот скачал триальную 60-дневную версию Flash Builder 4. И что же вы думаете? Разочарование. В принципе функционал один и тот же, даже в чем-то хуже, чем у предшественника. Связано это с казалось бы благой целью — исключить из списка циклические ссылки, те что в третьей версии заставляли ходить по кругу. Проблема заключается в том, что компоненты имеющие циклические ссылки собираются гораздо хуже, и ничего, впрочем, не мешает эти ссылки обнулить, ускорив в разы сборку мусора.

По моим наблюдениям, сборщик мусора достаточно ленив, и сколько-нибудь сложные для сборки вещи он откладывает на потом (т.е. фактически никогда не соберет), в то время как простые объекты без ссылок охотно собираются.

Работает новый профайлер медленне существенно, и потребляет больше памяти (словил пару раз OutOfMemory). Больше никаких достоинств замечено не было.
Таким образом, имеет смысл пользоваться профайлером из Flex Builder 3, если нет других объективных причин этого не делать.

, , , ,

Оставить комментарий