Не можете наследовать от классов, определенных в RSL?

Примечание. Это проект Actionscript, а не проект Flex.

У меня есть класс A, определенный в RSL (технически это художественный актив, который я сделал в Flash IDE и экспортировал для actioncript. Затем весь.FLA был экспортирован как SWC/SWF).

В моем основном проекте у меня есть класс B, который наследуется от класса A. Проект компилируется нормально, без ошибок.

Однако, когда проект запускается и я пытаюсь создать экземпляр класса B, я получаю ошибку проверки. Однако создание экземпляра класса A работает очень хорошо:

import com.foo.graphics.A;   // defined in art.swf / art.swc
import com.foo.graphics.B;   // defined locally, inherits from A
...
<load art.SWF at runtime>
...
var foo:A = new A(); // works fine
var bar:B = new B(); // ERROR!
// VerifyError: Error #1014: Class com.foo.graphics::A could not be found.

Для справки вот как я загружаю RSL:

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onArtLoaded);
var request:URLRequest = new URLRequest("art.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
loader.load(request, context);

Класс B определяется следующим образом:

import com.foo.graphics.A;
class B extends A {}

1 ответ

Решение

Я не думаю, что это ошибка. Это больше проблема сцепления.

Ошибка верификатора не возникает при попытке создать экземпляр B, Это происходит, как только ваш основной SWF загружен и проверен игроком. Это важное различие. Чтобы понять, что я имею в виду, измените этот код:

var bar:B = new B(); 

в

var bar:B;

Вы все еще получите ошибку.

Я не знаю, как вы строите SWF, но из ошибки кажется очевидным, что A учебный класс (B's parent) исключается из SWF. Я могу воспроизвести это с помощью этого переключателя mxmlc:

-compiler.external-library-path "lib.swc"

Тем не менее, изменив его на:

-compiler.library-path "lib.swc"

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

Таким образом, если вы установите арт-библиотеку как внешнюю, компилятор может выполнить проверку типов, вы получите автозаполнение в вашей IDE и т. Д. B класс по-прежнему зависит от A будучи определенным, хотя. Итак, во время выполнения, A должен быть определен всякий раз, когда B впервые упоминается в вашем коде. В противном случае, верификатор обнаружит несостоятельность и взорвется.

В IDE Flash, когда вы связываете символ, есть опция "экспортировать в первый кадр". Это то, как ваш код экспортируется по умолчанию, но это также означает, что возможно отложить, когда игрок впервые ссылается на определение класса. Flex использует это для предварительной загрузки. Он загружает только небольшой кусочек SWF, достаточный для отображения анимации перед загрузчиком, в то время как остальная часть кода (который не "экспортируется в первом кадре") и ресурсы загружаются. Делать это вручную кажется немного громоздким, если не сказать больше.

Теоретически, использование RSL должно помочь, если я правильно помню, как работает RSL (идея заключается в том, что RSL должен быть загружен игроком прозрачно). По моему опыту, RSL - это королевская боль и не стоит хлопот (просто назовите несколько раздражающих "функций": вам приходится жестко кодировать URL-адреса, довольно сложно аннулировать кэши, когда это необходимо, и т. Д. Возможно, некоторые из проблем RSL ушли, и теперь все работает разумно, но я могу вам сказать, что я работаю с Flash начиная с Flash 6 и на протяжении многих лет, время от времени я бы развлекал идею использования RSL (потому что сама идея делает многое смысла, реализация в стороне), только чтобы отказаться от него после обнаружения одной проблемы за другой.

Возможность избежать этой проблемы (без использования RSL вообще) может иметь shell.swf, который загружает art.swf и, как только он загружается, загружает ваш текущий код. Поскольку к моменту загрузки вашего code.swf уже был загружен art.swf, верификатор найдет com.foo.graphics.A (в art.swf), когда он проверяет com.foo.graphics.B (в code.swf).

    public function Shell()
    {
        loadSwf("art.swf",onArtLoaded);
    }

    private function loadSwf(swf:String,handler:Function):void {
        var loader:Loader = new Loader();
        loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handler);
        var request:URLRequest = new URLRequest(swf);
        var context:LoaderContext = new LoaderContext();
        context.applicationDomain = ApplicationDomain.currentDomain;
        loader.load(request, context);              
    }

    private function onArtLoaded(e:Event):void {
        loadSwf("code.swf",onCodeLoaded);
    }   

    private function onCodeLoaded(e:Event):void {
        var li:LoaderInfo = e.target as LoaderInfo;
        addChild(li.content);

    }

В ваш текущий основной класс, добавьте этот код:

        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);

Переместите логику конструктора (если есть) в init метод, и он должен работать нормально.

Но что мне не нравится в этом подходе, так это то, что вам нужно создать еще один проект для оболочки.

Обычно я использую класс, который проксирует графический актив.

    private var _symbol:MovieClip;

    public function B() {
        var symbolDef:Class = ApplicationDomain.currentDomain.getDefinition("com.foo.graphics.A") as Class;
        _symbol= new symbolDef();
        addChild(_symbol);
    }

поскольку com.foo.graphics.A это просто графический актив, вам не нужно проксировать другие вещи. Я имею в виду, если вы хотите изменить x, y, widthи т. д. и т. д., вы можете просто изменить эти значения в прокси-сервере, и результат практически одинаков. Если в некоторых случаях это не так, вы можете добавить метод получения / установки, который фактически действует на объект прокси (com.foo.graphics.A).

Вы можете абстрагировать это в базовый класс:

public class MovieClipProxy extends MovieClip {

    private var _symbol:MovieClip;

    public function MovieClipProxy(linkagetName:String) {
        var symbolDef:Class = ApplicationDomain.currentDomain.getDefinition(linkagetName) as Class;
        _symbol = new symbolDef();          
        addChild(_symbol);
    }

    //  You don't actually need these two setters, but just to give you the idea...
    public function set x(v:Number):void {
        _symbol.x = v;
    }

    public function get x():Number {
        return _symbol.x;
    }
}

public class B extends MovieClipProxy {

    public function B() {
        super("com.foo.graphics.A");
    }


}    

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

Теперь, единственная проблема с этим подходом состоит в том, что имя связи в конструкторе B не проверяется компилятором, но так как он находится только в одном месте, я думаю, что это управляемо. И, конечно же, вы должны убедиться, что ваша библиотека ресурсов загружена, прежде чем пытаться создать экземпляр класса, который зависит от него, иначе он будет ожидать. Но кроме этого, это сработало довольно хорошо для меня.

PS

Я только что понял, что в вашем текущем сценарии это может быть более простым решением:

public class B extends MovieClip {

    private var _symbol:MovieClip;

    public function B() {
        _symbol = new A();
        addChild(_symbol);
    }

}

Или просто:

public class B extends MovieClip {

    public function B() {
        addChild(new A());
    }

}

Та же идея с прокси, но вам не нужно беспокоиться о создании объекта из строки с использованием домена приложения.

Другие вопросы по тегам