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);
// ------------
Вот и всё. Надеюсь, кому-нибудь эта информация пригодится. Если возникнут вопросы – задавайте в комментариях, постараюсь ответить.
Filed under:
flex by Hrundik