现在的位置: 首页 > 综合 > 正文

AS3应用程序模块化开发与ApplicationDomain – (转载)

2013年03月30日 ⁄ 综合 ⁄ 共 6602字 ⁄ 字号 评论关闭

 当程序越来越大,我们需要把它拆分成多个swf,在需要的时候动态加载。拆分时应该尽量把不同的类编译进唯一的swf,避免因swf文件增多而使整个程序的文件尺寸增大。按此原则可以拆分出以下两种swf,借助 ApplicationDomain 共享其代码和资源。

  • 模块(Module)
    按照程序逻辑,可以拆分出多个“功能模块”,如“注册”、“管理”等等;按照游戏或社区类程序的关卡或场景,可以拆分出不同的“场景模块”。这些模块不是主程序运行必须的,只在需要的时候加载。
  • 运行时共享库(RSL)
    主场景或者多个模块通用的资源,比如位图、声音、设计好的页面元素等,可作为“库”在主程序运行前加载。可以整套更换的皮肤(skin)只需先加载一套。

  ApplicationDomain 是存放AS3定义(包括类、方法、接口等)的容器。使用Loader类加载swf时可以通过指定 ApplicationDomain 参数将swf加载到不同的域(Domain):

var loader : Loader = new Loader();
var context : LoaderContext = new LoaderContext();

context.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);

context.applicationDomain = ApplicationDomain.currentDomain;

context.applicationDomain = new ApplicationDomain();
loader.load(new URLRequest("loaded.swf"), context);

  ApplicationDomain使用类似于显示列表(DisplayList)的树形结构。 相对于舞台(Stage) ,可以认为 ApplicationDomain 最根部的是系统域(system domain),包含 Flash Player 核心类定义。主程序所在的域(以下简称主域)就是它唯一的子域,类似于Stage下的文档类(Document Class)。
一个fla文档类里代码:

this.stage.addChild(mySprite);
this.addChild(myMC);
this.addChild(myShape);

  运行后的显示列表:
01.gif
ApplicationDomain 的类似结构:
02.gif

  • 加载到子域(模块)
    类似于“继承”,子域可以直接获得父域所有的类定义,反之父域得不到子域的。和继承关系不同的是,如果子域中有和父域同名的类,子域定义会被忽略而使用父域的定义。
  • 加载到同域(运行时共享库)
    类似集合里的合并关系。被加载swf里的所有类定义被合并到当前域中可以直接使用。和加载到子域相同,和当前域同名的定义也会被忽略。
  • 加载到新域(独立运行的程序或模块)
    swf载入指定域之前,先要检查该域及其父域中是否存在同名类,重复定义一概忽略。如果加载别人写的程序,或者使用旧版本的主程序加载新版本的模块,为避免类名冲突就要加载到新域独立运行以使用自己的类。

  模块加载到同域不是一样可以吗?为何要加载到子域呢?好处就在于,卸载一个加载到子域的模块时,只要确保清除所有到该模块的引用,模块的所有类定义将被垃圾回收(Garbage Collection)。
有两种方式可以访问 ApplicationDomain :

  • ApplicationDomain.currentDomain
    currentDomain是ApplicationDomain的静态变量,表示当前代码所在的域。该变量很奇特,在主程序里指向主域,在加载到子域的模块里则指向该模块所在的子域。虽然 ApplicationDomain 有个 parentDomain 属性,但子域已经自动获得了父域的类定义,所以通过 ApplicationDomain.currentDomain 就可以获取父域定义了——包括主程序和加载到主域的共享库。(注:系统域不可直接访问,主域和所有新域即系统域子域的parentDomain属性为
    null)
  • LoaderInfo类的applicationDomain属性
    此方式可以访问任何方式加载的swf的 ApplicationDomain。对于主程序来说,加载到同域的库定义已经存在于 ApplicationDomain.currentDomain ,而模块的类主程序一般用不到。所以这种方式个人不推荐使用。

  ApplicationDomain 的 hasDefinition() 方法判断某定义是否存在,getDefinition() 方法获取指定的定义。下面以一个 例子 来介绍
ApplicationDomain 的具体用法和应用程序的拆分。
本例 有四个swf,shell.swf是主程序,lib.swf是共享库,login.swf和result.swf分别是“登录”和“结果”模块,所有的视图元件都在共享库中。实际开发时可能有很多库,比如“位图库”、“音效库”、“模型通用库”等。“通用库”里存放多个模块共用的资源,比如此例中的背景元素。而各个模块独有的资源还是放在各自的swf中。
主程序首先将共享库加载到同域,完成后将“登录模块”加载到子域。主程序可以像操作普通的视觉对象(DisplayObject)一样操作加载的模块:监听事件、调用方法。因为编译器不会识别未定义的类,为使用强类型,建议为主类和模型定义相应的接口,使用少量的重复代码协助编程。

private function showModule(p_module : IModule) : void
{
    
if (this.m_moduleList[0] == "login.swf")
    
{
        
p_module.show(this);
        
p_module.addEventListener("login", this.onLogin);
    
} else
    
{
        
p_module.show(this, this.m_userName);
    
}
}

  模块“继承”了主程序和共享库的所有类和资源,可以通过 ApplicationDomain.currentDomain.getDefinition() 来获取相应的类。注意获取不存在的类会抛出一个 ReferenceError。

protected function getClass(p_name : String) : Class
{
    
try
    
{
        
return ApplicationDomain.currentDomain.getDefinition(p_name) as Class;
    
} catch (p_e : ReferenceError)
    
{
        
trace("定义 " + p_name + " 不存在");
        
return null;
    
}
    
return null;
}

  登录模块获取库中的界面元素,并在点击按钮后抛出事件。Event类不允许带参数,必须使用继承Event的自定义事件抛出参数。主程序可以把模块的自定义事件也编译进去(这样就增大了整个程序的文件尺寸),或者让监听模块事件的函数接受一个Objcet参数,以获取其动态属性。

private function onLogin(p_e : Object) : void
{
    
this.m_userName = p_e.userName;
    
var login : IModule = p_e.currentTarget;
    
login.removeEventListener("login", this.onLogin);
    
login.dispose();
    
this.loadSwf();
}

  主程序收到事件之后卸载注册模块,加载“结果模块”到子域,并将登录模块传出的”userName”参数传给结果模块。

public function show(p_parent : DisplayObjectContainer,
...
 rest) : void
{
    
var libClass : Class = this.getClass("net.eidiot.appDomainDemo.Libaray");
    
if (libClass != null) this.initUi(libClass, rest);
}
override protected function initUi(p_libClass : Class, p_rest : Array = null) : void
{
    
this.addUi(this.getClass(p_libClass.BG_NAME), "结果");
    
var resultFunc : Function = p_libClass.getResult;
    
var userName : String = p_rest[0];
    
this.addChild(resultFunc(userName));
}

  注意initUi()方法分别使用了共享库中Libaray类的静态属性BG_NAME和静态方法getResult()。但是直接调用此静态方法会报错,可以先用 resultFunc 变量取出此方法。详细内容请参考 源代码

 

使用 ApplicationDomain 类
 

使用 ApplicationDomain 类

ApplicationDomain 类的用途是存储 ActionScript 3.0 定义表。SWF 文件中的所有代码被定义为存在于应用程序域中。可以使用应用程序域划分位于同一个安全域中的类。这允许同一个类存在多个定义,并且还允许子级重用父级定义。

在使用 Loader 类 API 加载用 ActionScript 3.0 编写的外部 SWF 文件时,可以使用应用程序域。(请注意,在加载图像或用 ActionScript 1.0 或 ActionScript 2.0 编写的 SWF 文件时不能使用应用程序域。)包含在已加载类中的所有 ActionScript 3.0 定义都存储在应用程序域中。加载 SWF 文件时,通过将 LoaderContext
对象的 applicationDomain 参数设置为 ApplicationDomain.currentDomain,可以指定文件包含在 Loader 对象所在的相同应用程序域中。通过将加载的 SWF 文件放在同一个应用程序域中,可以直接访问它的类。如果加载的 SWF 文件包含嵌入的媒体(可通过其关联的类名称访问),或者您要访问加载的 SWF
文件的方法,则这种方式会很有用。如以下示例所示:

package { import flash.display.Loader; import flash.display.Sprite; import flash.events.*; import flash.net.URLRequest; import flash.system.ApplicationDomain; import flash.system.LoaderContext; public class ApplicationDomainExample extends Sprite { private var ldr:Loader; public function ApplicationDomainExample() { ldr = newLoader(); var req:URLRequest = new URLRequest("Greeter.swf"); var ldrContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain); ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler); ldr.load(req, ldrContext); } private function completeHandler(event:Event):void { ApplicationDomain.currentDomain.getDefinition("Greeter"); var myGreeter:Greeter = Greeter(event.target.content); var message:String = myGreeter.welcome("Tommy"); trace(message); // Hello, Tommy } } }

使用应用程序域时,还要记住以下几点:

  • SWF 文件中的所有代码被定义为存在于应用程序域中。主应用程序在"当前域"中运行。"系统域"中包含所有应用程序域(包括当前域),也就是,它包含所有 Flash Player 类。
  • 所有应用程序域(除系统域外)都有关联的父域。主应用程序的应用程序域的父域是系统域。已加载的类仅在其父级中没有相关定义时才进行定义。不能用较新的定义覆盖已加载类的定义。

下图显示了某个应用程序在单个域 (domain1.com) 中加载多个 SWF 文件的内容。根据加载内容的不同,可以使用不同的应用程序域。紧跟的文本说明用于为应用程序中的每个 SWF 文件设置适当应用程序域的逻辑。

 

主应用程序文件为 application1.swf。它包含从其它 SWF 文件加载内容的 Loader 对象。在此方案下,当前域为 Application domain 1。用法 A、用法 B 和用法 C 说明了为应用程序中的每个 SWF 文件设置适当应用程序域的不同方法。

用法 A:通过创建系统域的子级划分子级 SWF 文件。在示意图中,Application domain 2 创建为系统域的子级。application2.swf 文件在 Application domain 2 中加载,因此其类定义从 application1.swf 中定义的类中划分出来。

此方法的一个用处是使旧版应用程序能够动态加载相同应用程序的更新版本,而不会发生冲突。之所以不发生冲突,是因为尽管使用的是同样的类名称,但它们划分到不同的应用程序域中。

以下代码将创建作为系统域子级的应用程序域:

request.url = "application2.swf"; request.applicationDomain = new ApplicationDomain();

用法 B:在当前类定义中添加新的类定义。module1.swf 的应用程序域设置为当前域 (Application domain 1)。这可让您将新的类定义添加到应用程序的当前一组类定义中。这可用于主应用程序的运行时共享库。加载的 SWF 被视为远程共享库 (RSL)。使用此方法可以在应用程序启动之前使用预加载器加载 RSL。

以下代码将某应用程序域设置为当前域:

request.url = "module1.swf"; request.applicationDomain = ApplicationDomain.currentDomain;

用法 C:通过创建当前域的新子域,使用父级的类定义。module3.swf 的应用程序域是当前域的子级,并且子级使用所有类的父级的版本。此方法的一个用处可能是作为一个使用主应用程序的类型的多屏幕丰富 Internet 应用程序 (RIA) 模块,该模块作为主应用程序的子级加载。如果能够确保所有类始终更新为向后兼容,并且正在加载的应用程序始终比其加载的软件的版本新,则子级将使用父级版本。如果可以确保不继续拥有对子级 SWF 的引用,则拥有了新的应用程序域还使您能够卸载所有的类定义以便于垃圾回收。

此方法使加载的模块可以共享加载者的 singleton 对象和静态类成员。

以下代码将创建当前域的新子域:

request.url = "module3.swf"; request.applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);

抱歉!评论已关闭.