пятница, января 18, 2008

Делаем прелоадер для Flex-приложения. AS3.

Если при разработке проекта нам доступен для редактирования таймлайн основного SWF, например, мы делаем проект во Flash IDE, всё просто - всё делаем как и раньше - а точнее - как нам советует в своей статье "Экспорт классов во второй кадр и создание прелоадера во Flash CS3" __etc.
А что делать, если наш проект компилируется Flex SDK?
Можно сделать маленький модулек, который будет загружать и мониторить загрузку основного SWF. Ну а если требуется наличие только одного SWF?

Люди советуют почитать статью Preloaders in AS3. Читаем.

Основную мысль автору статьи, подсказал Ted Patrick, намекнув на мета-тег [Frame] и поглядеть исходник mx.core.Application.
Поглядим-ка
"...\Flex2SDK\frameworks\source\mx\core\Application.as ".
Как отметил 101 (тот что автор статьи) там и правда есть тег [Frame(factoryClass="mx.managers.SystemManager")]. А в комментарии, по-моему, ключевой является фраза "Все фреймворки инициализируются SystemManager-ом".

Идем смотреть
"...\Flex2SDK\frameworks\source\mx\managers\SystemManager.as".
И правда - видим интересные фразы типа "ВНИМАНИЕ: Минимизируйте не флэшовые классы, которые импортируете здесь, всё что связано с SystemManager будет загружено в 1й фрейм до того, как загрузится прелоадер, и вообще, что-либо отображающееся." Кроме того, несколько ниже, говорится, что "SystemManager - первый визуальный класс, который создается в приложении. Он так же отвечает за создание mx.preloaders.Preloader, который отображает mx.preloaders.DownloadProgressBar до конца загрузки приложения, после чего SystemManager создает mx.core.Application instance". Собственно, вызывается метод SystemManager.create, код которого приводит 101 (автор).
Он пришел к следующим мыслям:
  • Сначала создаем основной класс MainClass как обычно и указываем компилятору чтобы он его компилил.
  • Затем в этом классе вставляем тег [Frame(factoryClass="MyFactoryClass")], который указывает на некий другой класс MyFactoryClass.
Это должно привести к тому, что скомпилируется SWF с двумя фреймами! Всё барахло, которое мы имбедим, наш основной класс, другие классы которые он пользует - всё это будет импортироваться во второй фрейм. Единственное, что будет импортировано в первый фрейм - наш класс MyFactoryClass и всё что он пользует. Шикарно!

Итак, нам остается всего-навсего создать класс обычного прелоадера, который:
  • Делает stop();
  • Мониторит загрузку любым удобным для нас способом.
  • После полной загрузки делает nextFrame();
Теперь мы можем инстанцировать основной класс используя getDefinitionByName() {А откуда?}. Это дает нам возможность контролировать, какой класс мы хотим сделать основным. Кроме того, теперь мы можем выгрузить прелоадер.
Еще одна любопытная особенность этого решения - основной таймлайн представляет MainClass, но в качестве "Document Class" теперь выступает не MainClass, а MyFactoryClass. Из этого следует:
  • MyFactoryClass должен наследоваться от MovieClip. И не в коем случае не от Sprite.
  • При инстанцировании MainClass, необходимо добавить (addChild) его в дисплей-лист MyFactoryClass.
  • MainClass не будет являться корнем дисплей-листа. Он будет чайлдом MyFactoryClass.
  • В своем конструкторе, MainClass не должен ссылаться на "stage", так как он будет доступен только после добавления (addChild) MainClass в дисплей-лист.
Теперь, всё, что осталось для нас неясным, развеем примером: код примера.

В основном классе FrameTest - это то что выше называлось MainClass - всё просто. Имбедится и выводится картинка "big_asset.jpg", желательно очень большого размера. Единственное необычное - это фраза [Frame(factoryClass="MyFactory")].

Класс MyFactory - это наш вышеописываемый MyFactoryClass.
  • В конструкторе производится остановка тайм-лайна stop(), выставляются параметры stage и добавляется обработчик "ENTER_FRAME".
  • Обработчик onEnterFrame() рисует полосу состояния загрузки вычисляя процент загрузки: var percent:Number = root.loaderInfo.bytesLoaded / root.loaderInfo.bytesTotal;
  • При выполнении условия framesLoaded == totalFrames, объект отписывается от события "ENTER_FRAME", осуществляет переход на следующий фрейм nextFrame() и производит инициализацию init().
  • Метод инициализации init() получает определение основного класса var mainClass:Class = Class(getDefinitionByName("FrameTest")); и, если такой класс существует if (mainClass), инстанцирует его и добавляет в дисплей-лист: var app:Object = new mainClass(); addChild(app as DisplayObject);.

Всё. Переходим к практике.

* * *

Не забудем положить картинку "big_asset.jpg" в папку с классами.

Первая коррекция - getDefinitionByName вызывает исключение, в случае если класс не найден. Поэтому проверки if(mainClass) недостаточно. Нужна обработка исключения try ... catch.

* * *

Пробуем trace(this.currentFrame);. Он выдает правильно - 1 из конструктора MyFactory, 2 - из init();.

Попробуем, как это будет работать в интернете. Хм. Прелоадер отрабатывает, но как-то поздно. Мой Naviscope показывает, что SWF загружается больше чем на половину, когда только начинает происходить отрисовка полосы загрузки. Попробую еще утяжелить SWF-ку. Для этого добавляю картинок.

Интересное наблюдение - если накопировать одну и ту же картинку и имбедить копии, размер SWF не изменится - как будто внедрена одна картинка! Вот это сжатие!

Итак, утяжелил до 20 мБ. Теперь всё встало на свои места. Прелоадер аккуратно отрабатывает объем и затем отображается картинка! Всё дело было в слишком быстром интернете.

Итак, подход прост и практичен. Спокойно делаем правильные и эффектные флэшки любого размера!

15 комментариев:

Unknown комментирует...

Хорошая статья, как раз то, что мне нужно было. Но не все так гладко. Вообщем выходят ошибки при компиляции и swf-файл не создается. все делаю как описано, в чем может быть проблема?
Сообщения:

Error: Unable to resolve resource bundle "skins" for locale "en_US".

Error: Unable to resolve resource bundle "styles" for locale "en_US".

Error: Unable to resolve resource bundle "skins" for locale "en_US".

Error: Unable to resolve resource bundle "core" for locale "en_US".

Error: Unable to resolve resource bundle "core" for locale "en_US".

Error: Unable to resolve resource bundle "effects" for locale "en_US".

Error: Unable to resolve resource bundle "core" for locale "en_US".

Анонимный комментирует...

я делал в коне так:
var stage:Stage = super.stage;
var loaderInfo:LoaderInfo = super.loaderInfo;
if (super.parent) {
super.parent.removeChild( this );
}
if (loaderInfo.hasDefinition("MainClass")) {
var C:Class = loaderInfo.getDefinition("MainClass") as Class;
if (C) {
var root:DisplayObject = new C() as DisplayObject;
stage.addChild( root );
}
}

Unknown комментирует...

2 Vietnam: Мда... Без ложка дегтя не обошлось. А ты чем компилируешь? Я проверял на Flex 2 SDK. Надо будет попробовать под 3ю версию. Любопытная ошибочка :)

2 BlooDHounD: Ага, то есть ты MainClass прикрепить к stage а не к фабрике, а фабрику расстрелять. Вполне логично - попробую.

Unknown комментирует...

2 Racer: Использую Flex 3 SDK 3

2 BlooDHounD: Не помогло! Ошибки так и лезут! :(

"MainClass" я так понимаю это полный путь к классу!

Unknown комментирует...

Так и не поможет - тут явно другая причина.
Гугл дает такие ссылки:
http://bugs.adobe.com/jira/browse/SDK-11329
Описание бага, но не понятно, как его вылечили.

На
http://www.boostworthy.com/blog/?p=142
Говорят:
Добавил в build.properties:

flexlib.dir=frameworks/libs
flexlocale.dir=frameworks/locale/en_US

Необходимо изменить "en_US" если используется другая локаль.

В build.xml он добавил в -library-path эти значения:
Всё работает.

Тут http://www.deitte.com/archives/2006/10/using_resource.htm про аналогичные ошибки говорят:

В Flex 3 было много изменений относительно ResourceBundle. И посылает сюда: http://labs.adobe.com/wiki/index.php/Flex_3:Feature_Introductions:_Runtime_Localization

Тут много чего написано про локали, но прямого ответа нет.
Попробуй покопать в этих местах - может быть вылечится?

Анонимный комментирует...

to Vietnam, мой пост не для вас :)
я этот эффект со стэджем давно юзаю.

Unknown комментирует...

2 BlooDHounD: А ты компилил свой код Flex 3 SDK? Не сталкивался с подобной ошибкой?

Unknown комментирует...

конечно компилил, я только под ним и работаю %) если ты про ошибку Vietnam, то у него проблемы с тем, что он скорее всего покоцал фремворк. у него нету файлов локацизации. тут: \sdks\3.0.0\frameworks\locale\en_US\
или они просто битые.

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

Unknown комментирует...

Спасибо! Насчет 3-его кадра - любопытно.

Unknown комментирует...

про extraClass тут написно:
http://nondocs.blogspot.com/2007/04/metadataframe_22.html

но флекс всёравно себя странно ведёт с ним.

Анонимный комментирует...

Кто-нить с последним Flex SDK проверял этот хак? 101 пишет, что вроде как он больше не работает и другого решения не знает.

Unknown комментирует...

Честно говоря, я так этим способом толком и не пользовался. Есть еще статейка, продолжение. Вот это я пользовал, вроде не так давно.
Кстати, в 9й бэте FlashDevelop, для чистых AS3 приложений уже есть встроенный шаблон приложения с прелоудером, где достаточно только подставить свой код в уже заготовленный класс.

Unknown комментирует...

Добрый день.
Можете пояснить строчку:
[Embed(source="big_asset.jpg")]private var Asset:Class;

Я так понял создаете класс для картинки которая не находится даже в библиотеке.
Пытаюсь делать тоже самое, выдает ошибку "1013: The private attribute may be used only on class property definitions.".
В чем проблема может быть?

Unknown комментирует...

Эта строчка внедряет в swf изображение, определяет его как класс, который затем можно инстанцировать. Ошибка означает что это определение должно находиться в пределах определения класса.

Анонимный комментирует...

Ребят, а в SDK4 в FDT4 соответственно кто нибудь проверял? У меня не видит stage, в основном классе )