Preloading in three frames system.
Для начала несколько слов прелюдии.
Примерно полтора года назад я накодил простенький передзагрузчик (аналог), стартующий до инициализации главного класса приложения, и периодически его дополнял и частично переписывал. Таким образом один класс вырос в целую систему, поддерживающую модульную архитектуру, полиморфизм, и предзагрузку необходимых ассетов.
Эту разработку я частенько сам использую в больших многомодульных проектах, где есть одна или несколько библиотек (swc) и огромное количество модулей, эти библиотеки использующие; Предзагрузчик у всех модулей - один, следовательно вынесен в библиотеку, а не клонируется по всей команде разработчиков от проекта к проекту. При этом (частая ситуация) хорошо было бы, что бы все модули приложения могли работать как самостоятельно, так и внутри любого другого модуля, - т.е. полиморфизм. Вот эту ситуацию и возьму за основу.
По-сути, мое решение представляет из себя неслабое универсализированное расширение уже давно описанных метод: тут (по-русски), тут и тут - настоятельно рекомендую ознакомиться перед продолжением.
Теория.
Добавляя в Главный Класс метадату:
[Frame(factoryClass="ru.kozlovskij.preload.Preloader")]
мы получаем вот такой результат:
[Схема с нашим классом]
Но все было бы чудесно, если б не одна проблема: Наш предзагрузчик понятия не имеет об имени главного класса приложения, который ему надо инстанциировать, а заниматься жестким хардкодом в большом проекте - непозволительная роскошь. Первая мысль была такова:
В каждом проекте-модуле использовать в качестве предзагрузчика субкласс того основного, что находится в библиотеке и остается неизменным. В этом субклассе мы просто переопределяем значение protected переменной MAINCLASS_NAME, ранее заведенной в основном классе и далее используем ее при инстанциировании нужного нам главного класса приложения. Но это мне вскоре надоело (неудобно) и было придумано более интересное решение:
Предзагрузчик наш остается в библиотеке, не имеет никаких субклассов (можно даже сделать его final). После загрузки всей свифки он сам найдет нужное ему имя класса в метаданных, а в голове главного класса мы просто впишем свой кастомный метатэг:
[App(className='ИмяГлавногоКласса')]
Что бы компиллер "увидел" наш метатэг, в параметры компилятора (в свойствах проекта) впишем:
-load-config cfg.xml
(про параметры компилятора подробности тут)
А в конфиге мы явно укажем, что на наш метатэг стоит обратить внимание:
cfg.xml:
<flex-config>
<compiler>
<keep-as3-metadata>
<name>App</name>
</keep-as3-metadata>
</compiler>
</flex-config>
Main.as - главный класс нашего приложения:
package { import flash.display.FrameLabel; import flash.display.MovieClip; import flash.events.Event; import flash.utils.*; import ru.kozlovskij.core.Application; import ru.kozlovskij.preload.Preloader; /** * SWF metadata */ [SWF(backgroundColor='0xFFCC00', width='500', height='500')] /** * The frameworks must be initialized by SystemManager. * This factoryClass will be automatically subclassed by any * MXML applications that don't explicitly specify a different * factoryClass. * * NOTE: Minimize the non-Flash classes you import here. * Any dependencies of SystemManager have to load in frame 1, * before the preloader, or anything else, can be displayed. */ [Frame(factoryClass='ru.kozlovskij.preload.Preloader')] //[Frame(factoryClass='exampleClasses.MyPreloader')] /** * Custom metadata tags */ [App(type='module', className='Main')] [copyrights(firstName='Aleksandr', lastName='Kozlovskij', url='http://Kozlovskij.ru', blog='http://FlashImp.ru')] public class Main extends Application { public function Main() { super(); addEventListener(Preloader.PRELOADER_DONE, preloaderDoneHandler); } private function preloaderDoneHandler(e:Event):void { //initialize... for(var i:int; i < (parent as MovieClip).currentLabels.length; ++i) trace('FrameLabel:', '[', FrameLabel((parent as MovieClip).currentLabels[i]).name, ',', FrameLabel((parent as MovieClip).currentLabels[i]).frame, ']'); (parent as MovieClip).nextFrame(); parent.addChild(new (flash.utils.getDefinitionByName('exampleClasses.CustomFrame') as Class)()); } } }
Метод preloaderDoneHandler вызывается, когда от предзагрузчика мы получаем событие об окончании загрузки всего необходимого (+ассеты?). В preloaderDoneHandler мы, удовлетворения ради, выводим в трэйс все, имеющиеся в нашем распоряжении (в родителе-предзагрузчике) фрэймы. В последних двух строках мы переходим в третий кадр и инстанциируем класс, который был загружен последним - об этом чуток ниже.
Во многих моих реализациях данного подхода я либо создавал в Main классе метод, возвращающий массив урлов к тому, что еще необходимо загрузить, что было несколько криво и не гибко, либо записывал эту информацию в метаданные, что позволяло предзагрузчику все необходимое скачать еще до инициализации главного класса, а он в свою очередь рождался "на всем готовом". Нюансы реализацию такой инициализации приложения я покажу позже, а сейчас - простой пример того самого предзагрузчика:
Preloader.as - собственно, сам прелоудер:
package ru.kozlovskij.preload { import flash.display.DisplayObject; import flash.display.MovieClip; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.system.ApplicationDomain; import flash.utils.ByteArray; import ru.etcs.utils.ClassExplorer; [Event(name="preloaderDone", type="ru.kozlovskij.preload.Preloader")] public class Preloader extends MovieClip { public static const PRELOADER_DONE:String = 'preloaderDone'; protected var MAINCLASS_NAME:String; protected var preloaderGraphics:IPreloaderGraphics; private var Main:Class; private var main:DisplayObject; protected var _standalone:Boolean = true; protected function get standalone():Boolean {return _standalone;} public function Preloader(preloaderGraphics:IPreloaderGraphics = null) { stop(); this.preloaderGraphics = preloaderGraphics ? preloaderGraphics : new PreloaderGraphics(); if(stage) stage.scaleMode = StageScaleMode.NO_SCALE, stage.align = StageAlign.TOP_LEFT, stage.frameRate = 31, createGraphics(), stage.addEventListener(Event.RESIZE, resizeHandler); else _standalone = false; addEventListener(Event.ENTER_FRAME, enterFrameHandler); resizeHandler(); } protected function createGraphics():void { addChild(preloaderGraphics as DisplayObject); } private function enterFrameHandler(e:Event):void { //percent = current / total var percent:Number = root.loaderInfo.bytesLoaded / root.loaderInfo.bytesTotal; preloaderGraphics.percent = percent * 100; //trace(Math.round(100 * percent) + '%'); if(framesLoaded == totalFrames) { removeEventListener(Event.ENTER_FRAME, enterFrameHandler); //Move to the next frame when we're done //Moving to frame 1(!) nextFrame(); preinit(); } } protected function resizeHandler(e:Event = null):void { preloaderGraphics.x = stage ? stage.stageWidth / 2 : root.width / 2, preloaderGraphics.y = stage ? stage.stageHeight / 2 : root.height / 2; } private function preinit(mainclass_name_from_metadata:String = null):void { var needThrow:Boolean = true; MAINCLASS_NAME = mainclass_name_from_metadata ? mainclass_name_from_metadata : MAINCLASS_NAME ? MAINCLASS_NAME : null; MAINCLASS_NAME ? null : (needThrow = false, getMETAMainClassName()); if(needThrow && !MAINCLASS_NAME) { clear(); throw new Error(this.toString() + ': Main Class Definition eq null'); } var has:Boolean = ApplicationDomain.currentDomain.hasDefinition(MAINCLASS_NAME); if(has)//if main app Main = ApplicationDomain.currentDomain.getDefinition(MAINCLASS_NAME) as Class; else { has = loaderInfo.applicationDomain.hasDefinition(MAINCLASS_NAME); if(has)//if module app Main = loaderInfo.applicationDomain.getDefinition(MAINCLASS_NAME) as Class; } if(has) { //and instantiate our Main class main = new Main(); //get urls, whot need to additional preloading before starting application (in next post..) init(); clear(); } else if(needThrow) { clear(); throw new Error(this.toString() + ': Main Class Definition NOT FOUND'); } } private function init():void { //finally we add main class to the DisplayList and dispatching event addChild(main as DisplayObject).dispatchEvent(new Event(PRELOADER_DONE)); //stage.addChild(main as DisplayObject) - only if we make a standalone app. } public function clear():void { //killing & clearing preloaderGraphics :) IPreloaderGraphics(removeChild(preloaderGraphics as DisplayObject)).clear(); standalone ? (stage.removeEventListener(Event.RESIZE, resizeHandler), stage.removeChild(this)) : null; } //-----------------------------------------------------// private function getMETAMainClassName():void { var bytes:ByteArray = new ByteArray(); bytes.objectEncoding = 3; bytes.writeBytes(loaderInfo.bytes); bytes.position = 0; var explorer:ClassExplorer = new ClassExplorer(); explorer.loadBytes(bytes); explorer.addEventListener(Event.COMPLETE, explorerWorkCompleteHandler); } private function explorerWorkCompleteHandler(e:Event):void { var explorer:ClassExplorer = e.target as ClassExplorer; var xml:XML = explorer.getDefinitionInfo(); //trace(xml.toXMLString()); var className:String; className = xml.definition/*.(@name == 'frame2')*/.script.classTrait.metadata.(@name == 'App').arg.(@key == 'className').@value.toString(); preinit(className); } } }
Как видно, в данной реализации я оставил возможность наследовать этот класс и переопределять значение переменной MAINCLASS_NAME. Так же в конструктор можно передать экземпляр визуализатора процесса загрузки, реализующий интерфейс IPreloaderGraphics. По умолчанию создается инстанс PreloaderGraphics - простейший субкласс DisplayObject'a, содержащий в себе текстовое поле для отображения процентов - это я тоже упростил:)
ru.kozlovskij.preload.PreloaderGraphics:
package ru.kozlovskij.preload { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFormatAlign; public class PreloaderGraphics extends Sprite implements IPreloaderGraphics { private var text:TextField = new TextField(); public function PreloaderGraphics() { super(); addChild(text); text.defaultTextFormat = new TextFormat('Tahoma'); text.defaultTextFormat.align = TextFormatAlign.CENTER, text.defaultTextFormat.size = 13, text.defaultTextFormat.letterSpacing = 1, text.defaultTextFormat.bold = true; text.borderColor = text.textColor = 0x0, text.selectable = !(text.border = true), text.width = 100, text.height = 50; percent = 0; } private var _percent:Number = 0; public function set percent(value:Number):void { _percent = value; text.text = 'Loading...\n' + uint(value) + '%'; } public function get percent():Number { return _percent; } public function clear():void { removeChild(text); } } }
Вернемся к ru.kozlovskij.preload.Preloader. Как это все работает?
Запускается конструктор, подписываемся на событие Event.ENTER_FRAME и постоянно проверяем, не завершилась ли загрузка; Загрузка завершена, находим в метаданные и в них - имя главного класса (preinit->getMETAMainClassName->explorerWorkCompleteHandler->preinit), инициализируем главный класс, не забыв перед этим перескочить на второй кадр, - вот такая последовательность.
В демонстрируемой реализации в качестве "поисковика метадаты" я использовал ClassExplorer, за что отдельное спасибо автору.
Для наших целей пришлось немного его подправить. Не помню уже где и что исправлял - интересующимся поможет любой мержер, например, Araxis Merge, который я считаю очень достойным.
Единственный минус - это то, что при каждом добавлении нового метатэга, необходимо прописывать его в конфиге компиляции.
Теперь от третьим кадре:
Для того, чтобы в нашем рутовом классе (рутовый он только в случае, если приложение самостоятельное, не модуль), являющимся по-совместительству предзагрузчиком, при компиляции создался третий кадр, конфигурационный файл необходимо дополнить следующим образом:
cfg.xml:
<flex-config>
<compiler>
<keep-as3-metadata>
<name>App</name>
</keep-as3-metadata>
</compiler>
<frames>
<frame>
<label>newFrameName</label>
<classname>exampleClasses.CustomFrame</classname>
</frame>
</frames>
</flex-config>
Класс exampleClasses.CustomFrame будет экспортирован во фрэйм "newFrameName". Третьим этот фрэйм будет потому, что два уже есть.
По непонятной мне причине, добавлять ноды для четвертого, пятого и т.д. фрэймов - бесполезно.
Теперь смотрим в Main.as на метод preloaderDoneHandler. Последние два строки - по-сути, тоже самое, что и в ru.kozlovskij.preload::Preloader, т.е. переход на следующий кадр и инициализация соответствующего класса.
Уф.. Вот и все. Наконец, исходный код (экспорт проекта).
П.С.: Совет: Не подписывайтесь на ProgressEvent.PROGRESS к собственному же loaderInfo!
Рубрики
Облачко
Архивы
- Август 2010 (1)
- Июнь 2010 (1)
- Апрель 2010 (1)
- Март 2010 (1)
- Февраль 2010 (1)
- Декабрь 2009 (4)
- Ноябрь 2009 (3)
Google Friend Connect
Движуха
- Guest на It’s Adobe :) LOL
- Александр Козловский на UINativeWindow
- Илья Маланьин на Preloading in three frames system.
- Александр Козловский на Preloading in three frames system.
- Илья Маланьин на Preloading in three frames system.
- Александр Козловский на Preloading in three frames system.
- etc на Preloading in three frames system.
- Александр Козловский на Preloading in three frames system.
- etc на Preloading in three frames system.
- Александр Козловский на Preloading in three frames system.
RAFPUG
UAFPUG
