Рубрика: ActionScript 3.0
6-ая RAFPUG
Автор фотографий - Алексей «Vooparker» Аникутин
На 6-ой встрече нашей RAFPUG Constantiner предложил сделать 30-минутный доклад о проблемах и решениях flash-клиента на портале top4top.ru. Я предложил своим коллегам Филиппу «FSB» Бондареву и Игорю «peace» Андрееву сделать наш доклад вместе (по сути переложил на них всю работу
), и так уж получилось что они вместо 25 минут рассказывали 2 часа.
Начал наш архитектор, Игорь. Одна из первых фраз была типа «то ли руки не оттуда ростут, то ли условия были таковы», на что Артемий предложил не впадать в уныние
Сотрудники Flexis соорудили доступ в интернет, и Игорь перешел от слайдов к показу живого сайта через Internet Explorer.
То ли за полгода мы так и не соорудили нормальное определение версий, то-ли и правда в IE был старый плеер, но все увидели предложение обновить плеер вместо top4top
Фидбек от Flexis? Какой там плеер в IE был? В Опере все сразу заработало. Следующие 20-30 минут Игорь ходил по top4top.ru в браузере и оказалось, что функционала-то немало.

Затем он перешел к более системным вещам и решениям, скоро тут появиться его презентация.
Настала очередь Филиппа. Он рассказывал про User Account, показывал запретные картинки, создавал и уничтожал планеты во вселенной.

Особенно запомнился момент, когда Филипп стал убирать красные глаза с помощью инструмента редактирования фото в User Account’е «Человеку-Жопе», символу IT-отдела портала:

Презентация Филиппа:
После короткого перерыва я за 5 минут протараторил свою презентацию:
Выводы (Филипп и Игорь со мной не согласны, их выводы я надеюсь тоже добавить в пост):
- Использование двух кривых версий (flash и html) одного контента - это победа технологий над здравым смыслом.
- На данный момент мне ясно, что лучшим является html решение с вставками flash там, где это абсолютно необходимо и с возможностью получить хоть что-то если FP недостаточно высокой версии
- Требование установить последний релиз плеера отпугнет от Вас бОльшую часть аудитории (если она состоит не из флешеров)
- Если не страшен предыдущий пункт и решено сделать портал на FP, то необходимо использовать Flex Framework
И, наконец, главный вывод:
- Необходимо предоставлять сервис максимального качества при текущей конфигурации машины пользователя, без необходимости установки обновлений чего-либо. Если он захочет (и у него есть такая возможность), то установит все, что попросите.

Я.
В целом получилось позитивно и, надеюсь, не очень занудно. Спасибо вам за вопросы и за то, что пришли.

Отвечаем на разные вопросы.
Настала очередь Артемия. Его доклад о нововведениях во Flash Player 10 Beta состоял из демонстрации множества примеров и активных обсуждений в зале. Мне больше всего понравилась генерация звука - вспомнился MS DOS, и сохранение файлов без участия сервера. Большое спасибо ему за доклад!

Профилирование ActionScript 3.0 проекта
Если Вы не раз профилировали проект, то что-то интересное, возможно, найдете ближе к концу поста.
Вступление
На данный момент единственный инструмент для профилирования это Flex Builder Profiler (хотя необходимый профайлеру API открыт - делай кто хочешь).
В процессе разработки top4top.ru не раз возникали проблемы с утечками памяти, иногда для их устранения приходилось даже вызывать разработчиков в офис по ночам. Иногда память не освобождалась вообще - росла с шагом 50-200 mb от числа переходов по страницам:

Это очень заметно и чинятся, как правило, за несколько часов. Сложнее сказать, есть ли утечка памяти, и где она по такому графику:

Тут уже мало что понятно. Поэтому первый вопрос - как определить, есть ли в приложении утечка памяти.
Определение факта утечки памяти - общие моменты
Тут нам очень поможет функция взятия разности между двумя dump-ами памяти приложения:

Результат - таблица Loitering Objects View, цитата из хелпа:
The Loitering Objects view shows you the differences between two memory snapshots of the application that you are profiling. The differences that this view shows are the number of instances of objects in memory and the amount of memory that those objects use. This is useful in identifying memory leaks. The time between two memory snapshots is known as the snapshot interval.
Для нас в ней самое интересное это имена классов и количество их оставшихся экземпляров.

На схеме выше приведен первый приходящий в голову вариант по поиску memory-leak-ов на Главной Странице. Однако он не работает - в список “бездомных” объектов попадают не только лики с Главной страницы, но и все объекты, создаваемые в Профайле 1.
Определение факта утечки памяти - стратегии
Можно придумать много разных стратегий. На картинке ниже прямоугольниками обозначены состояния системы, в которых инстанциируются объекты. При уходе из состояния его объекты должны удаляться. У нас в top4top состояния были страницами сайта, иногда - разделами (главная, чат).

Устранение утечки
- В простых случаях достаточно просмотреть код остающихся в памяти классов для устранения ошибки
Если она не очевидна, то тут нам поможет окошко Object References, которое показывает все ссылки на объект и открывается по дабл-клику на элементе в таблице Loitering Objects:

Сложнее всего искать утечки, из-за которых не удаляется вообще ничего. В этом случае два предыдущих способа не работают.
Именно такая ситуация у нас и была. Утечка находилась в часто исползуемом визуальном компоненте (назывался HtmlText). При создании компонента-страницы LayoutIndex в него где-то глубоко добавлялся экземпляр HtmlText и с тех пор вся иерархия (даже после удаления компонента всей страницы из дисплей-листа) становилась неуязвима для Garbage Collector’а.

Способ борьбы тут оказался довольно простой:
- package
- {
- import flash.display.Sprite;
- import flash.events.Event;
- public class test extends Sprite
- {
- private var picture:Picture;
- private var mainText:HtmlText;
- private var enterButton:SomeButton;
- private var closeButton:AnotherButton;
- public function test()
- {
- initContent();
- }
- private function initContent():void
- {
- picture = new Picture();
- addChild(picture)
- // Дальше в том же духе инициализируем и
- // добавляем другие компоненты
- // И подписываемся на удаление со сцены
- addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true);
- }
- // Удаляем все ссылки на дочерние элементы
- private function onRemovedFromStage(event:Event):void
- {
- picture = null;
- mainText = null;
- enterButton = null;
- closeButton = null;
- while (numChildren > 0)
- removeChildAt(0);
- }
- }
- }
Таким образом в памяти остаются только “текущие” объекты и мы с легкостью с помощью таблицы Loitering Objects их удаляем.
Делать подобные удаления всегда или добавлять этот код только в процессе устранения ликов - это отдельное решение.
Также у нас действует стандарт, что все подписки на события должны быть объявлены с Weak Reference, если нет четких противопоказаний. За весь проект случаев, когда нужна была подписка с жесткой ссылкой было всего несколько. false, 0, true!
Напоследок - регулярное выражение для поиска не-weak подписок: addEventListener\(([\w.\"]*), (\w*)\) (автор: Алексей «Vooparker» Аникутин).
Диаграммы классов ActionScript 3.0 и Flex - постеры.
Многие в курсе, но тем не менее: в далеком 2006-ом появились диаграммы классов по ActionScript 3.0 и Flex 2 в виде PDF документов, с которых можно напечатать постеры. Фрагмент Flex-диаграммы взят из поста “Ted On Flex”:

ActionScript 3.0 Class Diagram на Flex.org
Flex 2 Diagram на Flex.org
VooDoo подсказывает: Вышеперечисленные + еще куча постеров (Flex 3, AIR)
Напечатали ActionScript 3.0 диаграмму на цветном принтере на А3 с максимальным качеством, получилось мелко, видно только вплотную. Так что если есть возможность, лучше печатать на А2 или А1. На А3 со стандартным качеством вообще ничего не разобрать.
Баги в средствах разработки Flash Platform
Баги есть везде (для справки: шрединбаг, мандельбаг, борбаг), в том числе в mxmlc и Adobe Flex Builder 3. Попробую перечислить опасные и поедающие время баги - а это обычно те, которые абсолютно нелогичны (Ваши варианты в комментах).
- Ранний return в функции: если в начале функции поставить return, то в некоторых случаях это ведет к обрушению компилятора.
Memory-leak при использовании локальной переменной в конструкторе класса - она становится членом класса (гейзенбаг, сейчас повторить не удалось. Источник - Игорь “peace").
- public function ClassConstructor()
- {
- // Иногда при подобном объявлении у экземпляров
- // класса будет появляться поле clip и данный объект
- // не будет собран Garbage Collector'ом.
- var clip:MovieClip = new MovieClip();
- }
Если попробуете запустить профайлер на проект из 1 класса с одной строкой похожего кода в конструкторе то получите Runtime Exception c упоминанием некоего faramir’а
При использовании Embed-метатегов для включения .SWF-файлов в проект встроенные в этот файл шрифты часто (не при каждом запуске одного и того же приложения) заменяют собой аналогичные встроенные в родительский проект.
Например, если у Вас в asset-ах проекта есть надписи статическим текстом со сглаживанием, то при его embed-е Вы сможете пользоваться только теми буквами, что были включены в тот шрифт
(Источник - Филлип “FSB” Бондарев)При загрузке внешнего .SWF-файла с заранее (во Flash IDE) созданными там динамическими текстовыми полями Вам не удаться использовать в них встроенный шрифт основного ролика (Источник - Филлип “FSB” Бондарев).
Это справедливо также для случая, когда Вы вытаскиваете класс символа с существующими там динамическими текстовыми полями через ApplicationDomain.getDefinition() из загруженного файла и инстанциируете его уже в главном проекте
Подборка багов из Adobe Bug and Issue Tracking System
Ошибка компилятора:
- var num = 0.0.8;
Ошибка компилятора:
- function read():int
- {
- return 0;
- }
- function mouseClick(event:MouseEvent):void
- {
- var i: int;
- switch (i = read())
- {
- default:
- do
- {
- i = read();
- }
- while (i != 0);
- trace("OK");
- }
- }
Превращение в бесконечный цикл:
- function makeMeMyError():void
- {
- var a:Array = [];
- var i:uint = 0;
- while (false)
- {
- a[i++]=0;
- continue;
- }
- }
Ошибка компилятора:
- switch (param)
- {
- case 'one' :
- // the following line throws an #1010 Error
- var operations1:XML = company.department.(@title == "Operations")[0];
- trace(operations1);
- break;
- case 'two' :
- // no crash here
- var operations2:XML;
- operations2 = company.department.(@title == "Operations")[0];
- trace(operations2);
- break;
- }
- Пустой свич - ошибка компилятора:
- switch (i)
- {
- }
И финальный аккорд: обрушение браузера / виртуальной машины.
- "".lastIndexOf(null);
P.S: Я рассмотрел только баги компилятора и багоподобные особенности виртуальной машины. Issue по Flex Builder (их сейчас 795) и по другим продуктам к Вашим услугам всегда - Adobe Bug and Issue Tracking System.
Использование модулей
Проблема: как загружать код только тогда, когда он нужен? Например, на flash-сайте есть игры, но пользователь может до них и не дойти - поэтому их надо сделать подгружаемыми по требованию.
Решение: Flex project для этого есть модули.
Решение: ActionScript project можно банально подгружать другие .swf и вызывать публичные методы и свойства загруженных объектов, если заранее знать, что они там есть (при соблюдении Security). Мы получаем нетипизированный доступ, со всеми вытекающими из этого негативными последствиями:
- // Вы загрузили игру "Шашки" и теперь хотите ее начать за белых
- (loader.content as Object).start(true);
Есть простой способ сделать внешние модули строго типизированными с богатой историей использования. Пусть мы имеем отдельную .swf с нардами - backgammon.swf, главный класс - Backgammon имплементирующий IBackgammon). В основном проекте оставляем лишь IBackgammon, а сам код игры выносим в отдельный проект.
- // Вы загрузили игру "Нарды" и теперь хотите начать инициатором
- var iBackgammon:IBackgammon = IBackgammon(loader.content)
- iBackgammon.start(true);

Вот в кратце и все. Далее полученный при компиляции игры .swf загружаем во время выполнения (в runtime).
Дополнительно такими манипуляциями мы кроме размера основного .swf уменьшим время компиляции основного проекта.
Особенности: если вы не исключите интерфейс IBackgammon из бинарного файла модуля, то у вас наверняка возникнут ошибки при приведении загруженных данных к этому интерфейсу.
Кстати, в сами нарды вы можете поиграть на нашем портале top4top, в разделе общение. Там вам придется сначала найти себе собеседника и предложить ему партию.
Debug mode для FDT 3
В FDT 3 очень удобно работать, но в нем нет дебаг-режима. Способ дебагить, работая в FDT:
- Делаем две копии Eclipse:
- В одной установлен Flex Builder Plugin (далее - Flex)
- В другой - FDT 3 (далее - FDT)
- Во Flex создаем произвольный проект
- Добавляем в него папку нашего проекта через Link to the folder in file system
- Заходим в Run -> Open Debug Dialog…
- Заводим новый профиль во Flex Application
- Вкладка Main:
- Выбираем наш пустой проект, его главный файл
- URL or path to launch - убираем галочку с Use defaults и прописываем во все три поля путь к HTML-обертке нашего главного проекта. У меня это выглядит как “C:\projects\AS3_WS\Portal\bin\Main.html”
- Вкладка Source:
- Добавляем папки с кодом главного проекта (пути - абсолютные). У меня это “C:\projects\AS3_WS\Portal\src\” и “C:\projects\AS3_WS\Portal\lib\”
Все. Жмем F11, запускается наш FDT-билд с дебагом внутри Flex. Расставляем breakpoints во Flex, запускаем - работают! Стек вызовов, все дела. Ура!
RSL в ActionScript Project: разделяемый между приложениями код
Ситуация: в проекте несколько приложений, которые используют общий, довольно независимый код.
Например: серия игр, использующих 2D-движок APE или несколько видов сайта (главная, почта, чат), которые используют общие классы для работы с сервером статистики.
Проблема: каждая .swf содержит в себе этот код.
- Это увеличивает их размер
- При модификации этого кода нужно перекомпилировать все зависящие от него приложения (например, при изменении способа обращения к серверу)
Решение: вынести этот код в отдельный подгружаемый модуль. Во Flex для этого есть Runtime Shared Library (RSL), однако в ActionScript Project вам придется написать часть этой функциональности самому.

Это фишка именно Flex Framework. RSL-библиотеки подгружаются на этапе прелоадера: в классе mx.preloaders.Preloader есть методы, отвечающие за их загрузку. Какие RSL использовать и как их подключать (включить в код, подгружать в runtime) можно указать в меню Properties проекта. Flex также проверяет RLS-библиотеки на совместимость с текущей версией главного файла - для этого используются xml-манифесты - описания содержимого библиотеки. У нас этого нет, но мы с Вами можем реализовать, например, шифрование библиотек (если надо).
В ActionScript Project это нужно делать самому. Это нужно сделать прозрачно для приложения, т.е. все удобства работы с классами должны сохраниться:
- Подсказки в редакторе
- Использование ресурсов библиотеки без каких-либо проверок на загруженность (напр. классов или графики)
- Ничего не проверять и не загружать руками в каждом проекте отдельно
Создадим ActionScript Project, результирующий .swf которого будет содержать библиотеку ape.swc (воспользуемся опцией компилятора -include-libraries)
В наш основной проект также добавим эту библиотеку как .swc, однако выберем для нее Link Type: External (в ActionScript Build Path -> Library Path -> ape.swc -> Link Type), таким образом компилятор будет учитывать эти классы при компиляции, но не включит в .swf .
Выходной .swf проекта-обертки будем подгружать в прелоадере других основных проектов, до начала выполнения основных классов. Для каждого приложения будет собственный прелоадер, наследующий от класса DefaultPreloader (в нем - вся логика подгрузки и запуска). В конкретном прелоадере MyAppPreloader extends DefaultPreloader нужно будет указать массив адресов библиотек и имя главного файла приложения.
После этого в конструкторе главного класса приложения MyApp.as можно использовать все классы библиотеки, при этом они не включены в .swf проекта.
Пример на основе APE: на сайте можно скачать бинарный код ape.swc, но мы не можем загружать .swc файлы - только .swf .
Структура примера:
- PortalProto.swf - 3 kb
- UserProfile.swf - 5 kb
- lib/APE.swf - 20 kb
1. Создадим проект-обертку для ape.swc:
- Создаем обычный ActionScript Project, назовем APE
- Теперь нужно, чтобы система включила lib/ape.swc в APE.swf: добавим в ActionScript Compiler -> Additional compiler options -include-libraries “../lib/ape.swc” (путь к ape.swс относительно главного класса приложения APE.as)
Теперь у нас есть APE.swf, который содержит в себе все необходимые классы.
2. Создадим ActionScript проект SomeGame, cкопируем файл APE.swf в его папку bin/lib/. Создадим прелоадер, для этого напишем:
- package {
- // Импорты убраны для экономии места,
- // в коде их нужно дописать
- [SWF(width="800", height="600", backgroundColor="0xFFFFFF")]
- [Frame(factoryClass="SomeGamePreloader")]
- public class PortalProto extends Sprite
- {
- public function PortalProto()
- {
- trace("PortalProto::PortalProto");
- addEventListener(
- Event.ADDED_TO_STAGE,
- addedToStageHandler);
- }
- protected function init():void
- {
- stage.frameRate = 60;
- addEventListener(Event.ENTER_FRAME, run);
- APEngine.init(1/4);
- APEngine.container = this;
- APEngine.addMasslessForce(
