пятница, января 09, 2009

Что можно сделать с классом из подгружаемой SWF-библиотеки

А вот меня давно интересовал такой вопрос: можно ли подменить/модифицировать класс из подгруженного SWF, который был ассоциирован (или не был ассоциирован но определен по умолчанию) с клипом по средством "Export for ActionScript"?
Если не ошибаюсь, в ActionScript 2 такая возможность была.

Цель моей задачи такова: в некотором таймлайне подгружаемого SWF периодически появляется клип. При его появлении, с ним необходимо проделать какие-то действия.
В обычных условиях, с этим клипом ассоциируется класс (лежащий по соседству с FLA), в конструкторе которого и производятся эти действия.
Но вся прелесть-то в том, чтобы не было никаких скриптов ни в таймлайне, ни рядом с FLA. А всё что нужно делало бы загружающее этот SWF приложение - добавляло необходимый функционал подгруженному клипу.

Перерыл хелп и гугл - безрезультатно... Может быть не там искал. В итоге, модифицировать класс внешней библиотеки во время исполнения (бестолковое свойство prototype не в счет), либо подменить его не получилось, и ничего подходящего найти не смог.

Пока пойду обычным путем - класс клипа в конструкторе будет кидать событие с бабблингом: "Как получать сообщения от внедряемых/подгружаемых SWF. AS3".

* * *

Огромное спасибо BlooDHounD (см. комментарии). Его совет действительно работает.
Опробовал два метода:

  1. Класс, в подгружаемом SWF, задается в поле "Class" диалога "Linkage properties" для нашего клипа:
    classes.intrinsic.AssetCreationDispatcher
    При этом, никаких файлов классов поблизости с FLA создавать не требуется - класс создается "по умолчанию" (intrinsic - я назвал пакет этим словом, т.к. представленный метод мне отдаленно напомнил одноименный механизм в ActionScript 2.0).
    Затем, в загружающем приложении, я определяю класс classes.intrinsic.AssetCreationDispatcher но уже реальный, с необходимым кодом в AS-файле (в моем случае, это извещение приложения о создании клипа).
    Приложение загружает SWF, и при каждом появлении клипа в таймлайне ловит события от создающегося клипа, что показывает trace(
    ObjectUtil.getClassInfo(event.target).name)
    :
    classes.intrinsic::AssetCreationDispatcher
  2. Класс, в подгружаемом SWF, задается в поле "Base class" диалога "Linkage properties" для нашего клипа:
    classes.intrinsic.AssetCreationDispatcher
    Поле "Class" содержит имя класса "TestClip1".
    Создаю также другой клип с именем класса "TestClip2", и тем же "Base class".
    Приложение загружает SWF, и при каждом появлении вышеперечисленных клипов в таймлайне, в ловит события от создающегося клипа. Trace показывает уже:
    TestClip1
    TestClip2

Еще одна приятная возможность, которую дает нам такой прием:
Мы можем определить в классе-заглушке клипа загружаемого SWF ряд пустых методов (можно сказать интерфейс), а в рабочем классе загружающего приложения - этот же набор методов со всем необходимым кодом. В таймлайне наших клипов мы можем вызывать эти методы в любом месте, где только захотим, и они будут отрабатываться нашим главным приложением в соответствии с его кодом.
Конечно, для случая №1, все-таки нужно будет создать соответствующий AS-файл, иначе компилятор не пропустит вызовы неопределенных методов. Однако, для случая №2, создавать методы-заглушки даже не потребуется - компилятор пропускает такие вызовы. Ошибка времени исполнения будет генерироваться только в случае автономного выполнения загружаемого SWF. Но при выполнении его, будучи загруженным в приложение (которое, конечно, реализует весь набор методов), всё работает корректно.

Кроме того, важно, чтобы "совмещаемые" классы имели полностью эквивалентные имена, пакеты и цепочки вложенности пакетов. Иначе, они будут расцениваться как разные классы и не будут "совмещаться". И, конечно, загрузка производится в ApplicationDomain.currentDomain.
Еще одно важное замечание. Версия класса, которая включается в приложение, должна быть задействована каким-либо образом в коде приложения, иначе класс при компиляции не будет включен в приложение. Для этого, я сделал статический метод register(), который вызываю при инициализации приложения. Этот метод может ничего не делать - важно, что при наличии его вызова, класс будет внедрен в приложение при компиляции.

Еще одна достопримечательность этого метода: различные приложения, загружающие подготовленные таким способом SWF, могут реализовывать заготовленные в нем интерфейсы по-своему.

Еще раз спасибо BlooDHounD :). Вот что интересно - ведь такой подход имеет место быть не случайно, иначе, наверняка бы было предусмотренно какое-нибудь исключение, сообщающее о конфликте имен классов. Скорее всего, эта возможность даже имеет какое-нибудь название. Объяснение работоспособности этого подхода объясняется в документации к LoaderContext.applicationDomain (возможно объяснение есть где-то еще):
"Loader's own ApplicationDomain.
You use this application domain when using ApplicationDomain.currentDomain. When the load is complete, parent and child can use each other's classes directly. If the child attempts to define a class with the same name as a class already defined by the parent, the parent class is used and the child class is ignored.
" Другими словами, если существует конфликт имен классов, класс из загружаемого контента игнорируется.

* * *

Любопытная бага (предвидел, что будет что-то неладное). Оказывается слово intrinsic во Flex всё еще является ключевым (хотя в ActionScript 2.0 Migration про него написано, что его удалили) и выделяется жирным шрифтом. Любопытно, что автокомплит никак не хочет нормально работать с пакетами, обозванными этим именем, а в import-ах вообще начинает творится бардак. Лучше от него избавиться - может оно и к лучшему - тут подойдет что-нибудь типа classes.templates.
Кстати, порой приходится долго ломать голову над тем, как назвать тот или иной пакет/класс/метод/переменную. Иногда, размышляя над какой-нибудь сущностью (и вкапываясь в Lingvo), можно убить больше часа. Но оно стоит того.

4 комментария:

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

если мне память не изменяет, то ваша задача решает простым способом:
1. называем в библиотеке класс AAA.
2. допустим у нас есть некий класс родитель A.
3. можем сделать либо класс A-external либо просто пустым.
4. определяем наш класс A в главном свф.
5. при подгрузки класс AAA должен будет стать наследником класса A из главной свф.

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

можно обойтись и без метода register.
опять же несколько способов:
1. есть специальная директива компилятору "типа включи такой-то класс в свф".
2. можно просто написать сам класс в коде, без вызовов каких либо методов, это тоже будет являться экспрешеном. как если написать в коде "5;" компілятор не ругается. пишем так: "import package.Class; Class;" и он автоматом включится в проект.

кстати вторым способом включаются классы во флексе.

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

Супер, спасибо!
О первом способе я в курсе, но думаю, лучше избегать использования такого рода директив - переносимость и удобоваримость страдает.
А вот второй способ действительно изящен.

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

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!