Бага во Flex SDK 4.0, связанная с обработчиками событий и состояниями

13:07

Нашёл баг в релизной версии Flex SDK 4.0, которая приводит к утечке памяти. Если в вашей кастомной MXML-компоненте есть состояния и обработчик события, связанный с состоянием, то такая компонента навсегда останется в памяти.

Поэтому, не стоит использовать в промышленном коде обработчики событий с модификаторами состояния, как в примере:

<s:Button label="Test" click.main="trace('click!')" />

Вместо этого можно использовать обычный обработчик события с if-ом внутри:

<s:Button label="Test" click="if(currentState == 'main') trace('click!')" />

Я порепортил баг Адобам, проголосуйте, может, быстрее поправят: https://bugs.adobe.com/jira/browse/SDK-26185

Комментарии отключены

Monkey patches и Runtime Shared Libraries (RSLs)

03:08

Monkey patching – это замена кусков flex framework своими собственными классами. Например, можно скопировать mx.core.UIComponent в папку src вашего проекта, изменить его, и после этого флекс будет использовать этот изменённый класс, вместо оригинального класса из библиотеки.

Вообще, такая практика считается злом, и я с этой точкой зрения согласен. Но иногда просто необходимо прибегнуть к monkey patching, например, чтобы исправить баг фреймворка или как-то расширить его в случаях, когда наследование использовать невозможно.

И всё работает прекрасно до тех пор, пока вы не решите загружать фреймворк в виде RSL (Runtime Shared Library). После этого патчи работать перестанут.

Мы столкнулись именно с такой проблемой в нашем проект, CommuniGate Pronto!, и не смогли найти решение в интернете. Но в итоге нашли довольно уродливое, но при этом работающее решение.

Немного теории

Флэш-плеер обрабатывает загрузку реализаций классов следующим образов – в одном application domain может быть только одна реализация класса N. При этом, если в данном application domain уже есть реализация класс N, то ни при каких обстоятельствах в этом application domain она заменена не будет.

RSL (runtime shared library) – это просто .swf, в котором находятся определния и реализации каких-то классов. Framework RSL – это .swf с определениями и реализациями всех классов Flex Framework.

Флэш-приложение по своей природе разделено на кадры, как в мультике. Оно на самом деле и называлось до девятой версии плеера flash movie (мувик ;) ). К каждому кадру могут быть привязаны какие-либо данные. В том числе определения и реализации классов.

Флэш-плеер загружает приложение по кадрам. Как только первый кадр полностью загружается, флэш-плеер начинает его проигрывать – привязывать реализации классов к опредениям, выполнять код, показывать картинку, проигрывать звук и так далее.

Компилятор flex (mxmlc) строит приложение, состоящее из двух кадров. В первый кадр мувика он записывает минимально возможное количество классов и ассетов для того, чтобы показать прелоадер, а во второй кадр записывает все классы, используемые в приложении. В том числе и фреймворк.

Дак вот – RSL подгружаются в первом кадре загрузчиком. И становятся определены в первом кадре. А monkey patched-реализации классов находятся во втором кадре, и поэтому не оверрайдят уже загруженные из RSL исходные реализации.

Решение

Возможным решением этой проблемы является использование кастомного прелоадера (разумеется, у каждого уважающего себя приложения должен быть кастомный прелоадер), в который можно добавить ссылку на патченные классы. Таким образом эти классы (и всех их зависимости) включаются в первый кадр приложения. Но такое решение сильно увеличивает размер загрузчика, поэтому я не думаю, что это решение может кого-то устроить. Подробно это решение описано в блоге James Ward.

Более приемлемое решение становится очевидный, как только понимаешь, как работает flash-плеер.  В целом, нужно создать собственную RSL-библиотеку с патченными классами и сделать так, чтобы она загрузилась раньше RSL flex framework.

Создание monkey-patched библиотеки

Можно создать RSL с помощью встроенных фич флекс билдера. Создайте проект типа Flex Library, привяжите свой проект к этой библиотеке, и сделайте эту библиотеку загружаемой в виде RSL.  (project propertis -> Flex Build Path -> Library path -> Link type под папкой библиотеки). Этим путём можно получить только довольно жирную библиотеки, поскольку флекс включит в неё бОльшую часть фреймворка. Но если в вашем случае был пропатчен небольшой класс с небольшим количеством зависимостей, то этот путь вас вполне может устроить. В моём случае библиотека получалась чересчур жирной, поэтому я решил пойти сложным путём.

Известно, что mxmlc позволяет контроллировать, какие классы нужно включать в .swf, а какие нет. Эта функциональность просто необходима для модулей и RSL, и ей можно управлять с помощью флагов компилятора externs, -external-library-path, -includes, -load-externs и некоторых других. Подробнее об этом можно прочитать в официальной документации.

Идея в следующем – можно создать простое приложение с минимальным количеством зависимостей, заставить компилятор включить патченные классы в .swf, создать отчёт связывания (link report) и исключить из него все ненужные классы.

Будьте аккуратны – нельзя добавлять в это приложение зависимости от патченных классов, потому что это может всё сломать – в первом кадре flex framework ещё не загружен и зависимости не могут быть удовлетворены.

Код приложение должен выглядеть примерно так:

package
{
import flash.display.Sprite;
public class monkeylib extends Sprite
{
    public function monkeylib()
    {
          super();
    }
}
}

После этого нужно это приложение собрать и создать отчёт связывания:

"c:\Program Files\Adobe\Flex Builder 3\sdks\3.2.0\bin\mxmlc" -includes+="mx.controls.Button" -link-report=monkey_report.xml monkeylib.as

Далее нужно убрать патченные классы из отчёта и использовать его с -load-externs. Также нужно убрать главный класс приложения (monkeylib в нашем случае).

В качестве примера можно использовать класс Button.  Открываете monkey_report.xml, и с помощью Ctrl+F ищите строчку :Button:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script name="C:\Users\Nikita\Documents\Flex Builder 3\monkeylib\src\mx\controls\Button.as" mod="1254311407324" size="18581" optimizedsize="15328">
<def id="mx.controls:Button" />
<pre id="mx.core:IButton" />
<pre id="mx.managers:IFocusManagerComponent" />
<pre id="mx.core:IDataRenderer" />
<pre id="mx.core:UIComponent" />
<pre id="mx.controls.listClasses:IDropInListItemRenderer" />
<pre id="mx.core:IFontContextComponent" />
<pre id="mx.controls.listClasses:IListItemRenderer" />
<dep id="mx.styles:ISimpleStyleClient" />
<dep id="flash.events:Event" />
<!-- ..... -->
</script>

Весь этот блок нужно удалить, либо закомментировать.  Обратите внимание на идущие подряд теги <pre/>. По-моему, в данном случае ‘pre’ обозначает ‘prerequisite’, то есть это список жизненно необходимых классов – родительского класса и интерефейсов, которые этот класс реализует. Все эти классы также нужно включить в библиотеку, иначе ничего не получится и во время загрузки библиотеки вы увидите ошибку VerifyError.

Помимо этих блоков <script/>, нужно удалить (или закомментировать) все теги <pre/>, которые ссылаются на класс, который вы хотите включить в библиотеку. Иначе он не будет в неё включен.

Таким образом, для класса Button нужно включить ещё классы IButton, IFocusManagerComponent, IDataRenderer, IDropInListItemRenderer, IFontContextComponent, IListItemRenderer, UIComponent.  Обратите внимание на UIComponent, наличие этого класса приводит к тому, что нужно включить и все его родительские классы:  IPropertyChangeNotifier, ISimpleStyleClient, IStyleClient, IDeferredInstantiationUIComponent, IRepeaterClient, IValidatorListener, IInvalidating, ILayoutManagerClient, IUIComponent, FlexSprite, IFlexDisplayObject, IFlexModule, IConstraintClient, IToolTipManagerClient, IChildList, IAutomationObject, IStateClient, IUID.
Отчёт, который получился в итоге у меня, можно посмотреть здесь: monkey_report.xml

После этого нужно просто загрузить этот отчет с помощью -load-externs во время компиляции:

"c:\Program Files\Adobe\Flex Builder 3\sdks\3.2.0\bin\mxmlc" -includes+="mx.controls.Button" -load-externs=monkey_report.xml -link-report=libreport.xml monkeylib.as

Очень полезно снова использовать флаг -link-report – можно сразу посмотреть, какие именно классы были включены в библиотеку.
Если вы всё сделали правильно, то получится .swf-файл в 30kb (вместо 110kb, которые получаются без этих сложных процедур).

Загружаем пропатченную библиотеку до framework RSL

Хорошего пути решения этой проблемы я найти не смог – mxmlc всегда помещал RSL фреймворка раньше моей в списке загрузки. Поэтому снова пришлось прибегнуть к капельке черной магии манки патчинга. Я изменил класс mx.preloaders.Preloader; так, чтобы он помещал мою библиотеку в самое начало списка загрузки RSL. Тестовый проект можно сказат тут. Вот изменённые строки 179-184 класса Preloader:

// monkey patch
var monkeyRSLNode:RSLItem = new RSLItem("monkeylib.swf");
if(rslList.length > 0)
monkeyRSLNode.rootURL = RSLItem(rslList[0]).rootURL;
rslList.unshift(monkeyRSLNode);
// ------------

Вот и всё. Надеюсь, кому-нибудь эта информация пригодится. Если возникнут вопросы – задавайте в комментариях, постараюсь ответить.

2

Первый пост

03:07

Привет!
Добро пожаловать в мой блог. Меня зовут Никита Петров, я flash/felx – разработчик.

Попытаюсь писать только о неочевидных проблемах во flex и flash – решения очевидных можно легко найти в интернете и документации.

А ещё я знаю английский, и хотя мои познания языка далеки от идеального, я буду писать на двух языках – русском и английском. Буду стараться публиковать записи синхронно.

Комментарии отключены