以前做项目时,需要读取的UI资源都是放在同一个域(这里的UI使用了fl包中的控件),只要指定好Loader的应用程序域为当前域,即可使用fl包的定义来控制UI中的控件。 昨天遇到个问题,同事需要将这些待读取的资源放在不同的域中(比如CDN服务器),再使用如上方法时,资源中的类定义都能找到,但实例化后,MovieClip中的控件即无法取出。

开始以为是策略文件的问题,于是在资源所在域的根目录放入了 crossdomain.xml,内容如下:

<?xml version="1.0"?> 
<cross-domain-policy> 
    <allow-access-from domain="*" /> 
    <allow-http-request-headers-from domain="*" headers="*" /> 
</cross-domain-policy>

经测试后还是无法获取控件实例,然后开始翻手册,注意到了 LoaderContext 类初始化时的第三个参数 securityDomain,官方对此参数的解释如下:

仅当加载 SWF 文件(不是图像)时才会使用此属性。如果 SWF 文件所在的域与包含 Loader 对象的文件所在的域不同,则指定此属性。指定此选项时,Flash Player 将检查策略文件是否存在,如果存在,来自跨策略文件中允许的域的 SWF 文件可以对加载的 SWF 内容执行跨脚本操作。可以将 flash.system.SecurityDomain.currentDomain 指定为此参数。

于是在保持策略文件位置正确的前提下,为 Loader 的 LoaderContext 参数加入了 securityDomain 属性,指定为 SecurityDomain.currentDomain,语句如下:

var loaderContext:LoaderContext = new LoaderContext(true);
loaderContext.applicationDomain = ApplicationDomain.currentDomain;
loaderContext.securityDomain = SecurityDomain.currentDomain;
loader.load(new URLRequest("http://mydomain/sample.swf"), loaderContext);

编译运行出现如下错误:

SecurityError: Error #2142: 安全沙箱冲突:本地 SWF 文件不能使用 LoaderContext.securityDomain 属性。

于是,我把编译好的swf文件传到了公网的另一个域名下,然后在浏览器中打开了这个文件,UI资源被顺利加载并显示。 目前看来,在本地开发调试时,资源还是放在本地,等到发布的时候再把主文件和资源放在不同的公共域中。如果情况特殊,资源必须在公共域中,也可以在Load完成后,用一个新的Loader的loadBytes方法去获取之前Loader的contentLoaderInfo.bytes,也可以顺利获取组件实例,正式发布后去掉loadBytes这一步,给LoaderContext加上SecurityDomain.currentDomain属性即可。代码如下:

public class Test extends Sprite
{
  public function Test()
  {
    addEventListener(Event.ADDED_TO_STAGE, addedToStage);
  }

  private function addedToStage(e:Event):void
  {
    removeEventListener(Event.ADDED_TO_STAGE, addedToStage);
    // 这里把Loader(oldLoader)放在一个公用类中,是为了让FL包从该Loader的ApplicationDomain中获取皮肤的实例。
    // 因为皮肤都是MovieClip,没有跨脚本操作,所以公开这个Loader,否则就要公开newLoader
    // fl.core.UIComponent.as 中的 getDisplayObjectInstance 方法中:
    // classDef = getDefinitionByName(skin.toString()); 
    // 修改为:
    // classDef = Global.loader.contentLoaderInfo.applicationDomain.getDefinition(skin.toString());
    Global.loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onOldLoadComplete);
    Global.loader.load(new URLRequest("http://junnan.org/t.swf"), new LoaderContext(true, ApplicationDomain.currentDomain));
  }

  private function onOldLoadComplete(e:Event):void
  {
    var oldLoader:Loader = (e.target as LoaderInfo).loader;
    oldLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onOldLoadComplete);

    if (e.type == Event.COMPLETE)
    {
      // 用新的Loader来读取旧Loader的bytes数据
      var newLoader:Loader = new Loader();
      newLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onNewLoadComplete);
      newLoader.loadBytes(oldLoader.contentLoaderInfo.bytes);
    }
  }

  private function onNewLoadComplete(e:Event):void
  {
    var newLoader:Loader = (e.target as LoaderInfo).loader;
    newLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onNewLoadComplete);
    // 这里链接名为 S_Test 的MovieClip元件中含有一个实例名为 rbTest 的 RadioButton 组件。
    var testCls:Class = newLoader.contentLoaderInfo.applicationDomain.getDefinition("S_Test") as Class;
    var testMC:MovieClip = new testCls();
    var radioButton:RadioButton = testMC.getChildByName("rbTest") as RadioButton;
    radioButton.label = "junnan.org";
    addChild(testMC);
  }
}

参考