网上有不少flash,一般都不须要显示的使用应用程序域,由于默认的应用程序域就够用了。其实复杂的状况下须要用到应用程序域,好比说有两个不一样的swf,一个是旧版本的,一个是新版的,这两个文件里的类有相同的彻底限定明,以前的设计是旧的swf不要了,用新的swf代替,后来又要这两个swf同时加载到一个主程序里,那么这时就须要使用到应用程序域了。
html
和安全域同样,不一样安全沙箱下的SWF有着本身独立的类定义。这种在安全域下面进行划分和管理类定义(函数、接口和命名空间的定义也相似)的子域就是应用程序域。应用程序域只存在于安全域内,而且只能属于惟一的一个安全域。可是安全域能够包含多个应用程序域。跨域
安全域内的应用程序域安全
虽然安全域沙箱用于保护数据安全,应用程序沙箱域用于划分定义。可是他们都用于解决定义的冲突和判断代码的继承关系。
安全域彼此之间是相互独立的,相比之下,应用程序域之间的关系则较为复杂。应用程序域经过相似于Flash中的显示列表那样的层级关系连接在一块儿。应用程序域能够包含任意的子域,而子域只能有一个父域。子域继承了来自父域中的定义,就像是显示列表中父对象的位置和缩放属性被子对象继承同样。
应用程序域的根节点是一个系统域,这个域包含了Flash Player API的原生定义(Array,XML,flash.display.Sprite等等)。系统域与安全域是一一对应的关系,当安全域初始化的时候这个惟一的系统域也被创建。
当一个Flash Player的实例初始化的时候,SWF文件加到它对应的安全域内。同时也建立了一个包含了这个文件中全部编译过的ActionScript定义的应用程序域。这个应用程序域就成为安全域下的系统域的第一个子域。Flash Player API的原生定义就经过这种继承关系对全部子域开放。网络
在系统域下新建了一个SWF应用程序域架构
咱们将在 应用程序域的继承 章节中进行更多关于继承的讨论。app
第一个实例化Flash Player的SWF文件所包含的定义老是被加载为系统域的直接子域。父SWF去加载子SWF的时候,能够控制子SWF内的定义所要放置的位置。可选的位置共有如下4种:框架
前三种状况都是把子SWF加载到父域所处的安全域下,只有第四种是惟一一种把SWF加载到其余安全域下的方法。dom
加载子SWF时放置应用程序域的4种选择编辑器
还有一种没提到的方式,是你为某个已加载的SWF建立了应用程序域,再把其余子SWF中的定义合并到(或者继承)这个域的状况。这种特殊的放置方式须要复杂的应用程序域层级管理,你须要掌握 ApplicationDomain.parentDomain 的用法,在此提醒读者当心:这种方法一般在不一样的安全沙箱下(本地或者网络)会有不一样的行为。这种方式很不常见,因此在此不进行更深的探讨。
LoaderContext对象的 applicationDomain 属性定义了放置应用程序域的方式。你能够用 ApplicationDomain.currentDomain (相似于安全域的 SecurityDomain.currentDomain )或者用new关键字新建一个 ApplicationDomain 实例来做为参数。在ApplicationDomain的构造函数里能够为新建的域指定父域,若是这个参数没有指定,则表示将该域直接做为系统域的子域。函数
// 将定义放置到父SWF所在的应用程序域(当前应用程序域) var current:ApplicationDomain = ApplicationDomain.currentDomain; // 将定义放置到父SWF所在的应用程序域的的子域 var currentChild:ApplicationDomain = new ApplicationDomain(current); // 将定义放置到父SWF所在的应用程序域的系统域 var systemChild:ApplicationDomain = new ApplicationDomain();
下面的代码演示了使用LoaderContext对象传递ApplicationDomain实例给Loader.load方法,把一个子SWF加载到父SWF所处的应用程序域的子域下的例子。这种方式也是默认的加载行为。
var context:LoaderContext = new LoaderContext(); // 把子应用程序域做为当前应用程序域的子域 var current:ApplicationDomain = ApplicationDomain.currentDomain; context.applicationDomain = new ApplicationDomain(current); var loader:Loader = new Loader(); var url:String = "child.swf"; loader.load(new URLRequest(url), context);
ApplicationDomain实例在内部包含了不对ActionScript开放的层级位置信息。每一个ApplicationDomain实例都是一个惟一引用,彼此之间不能相互比较。
var current1:ApplicationDomain = ApplicationDomain.currentDomain; var current2:ApplicationDomain = ApplicationDomain.currentDomain; trace(current1 == current2); // false
你也不能经过parentDomain属性获得系统域的引用,只有经过new ApplicationDomain()才能够。
定义的继承和类继承有点相似,二者都是子级能够访问父级的定义,而反之则不行。
区别在于,应用程序域的继承不容许子级覆盖父级的定义。若是子域中包含有与父域同样的定义(指的是彻底限定名称一致,包括包路径)。那么父域中的定义会取代掉子域。
子域中的定义被父域覆盖
这是由于你不能改变一个已经存在的实例的类定义。若是新的定义在被加载进来之前就已经用旧的定义生成过实例,那么这个实例原先的类定义和新的类定义之间就会产生冲突。因此Flash Player保护原先的类定义不被重写来避免冲突。
这也意味着开发者不可能覆盖ActionScript API的原生定义。由于SWF所处的应用程序域确定是系统域的子域,而系统域包含了全部原生的定义。因此就算子SWF中包含了同名的定义,也会被系统域中的定义所覆盖。
当向一个已经存在的应用程序域合并定义时,上述规则一样适用。只有与原先的域里的定义无冲突的定义才会被合并。
新增到应用程序域里的定义不会覆盖现有的定义
这种状况下,那些发生冲突但却被覆盖的定义就彻底获取不到了。可是若是是继承的方式,就算子域中的那些冲突定义被父域中的定义覆盖掉,仍是能够经过getDefinition方法从子域中提取出来,关于这点将在 动态获取定义 章节中讨论。
加载到应用程序域中的定义在应用程序域的生命期里一直存在。在SWF卸载后,用于保存这个SWF内的定义的应用程序域也会从内存中卸载。但若是该SWF的定义是放在其余某个已经存在的应用程序域内的话,那么这些定义将一直存在于内存中,除非目标应用程序域所关联的那个SWF被卸载。若是一直把新的定义加载到一个已经存在的域内,好比为第一个被加载的SWF建立的域,那么定义所占用的内存就会一直增长。若是是一个不停加载子SWF的滚动广告应用的话,持续增长定义到相同的应用程序域内引发的内存增加问题显然不是预期的结果。
并且,用这种方式加载的定义不会随着子SWF的卸载而卸载,而是在第二次加载相同的子SWF的时候重用第一次加载时建立的定义。这一般不会有什么问题,可是这意味着再次加载相同SWF的时候静态类的状态不会重置。静态变量有多是上次使用过的值,必定会和第一次加载进来的时候保持一致。
因此,不一样的状况须要不一样的解决方法。
定义的继承机制使得子域能够很方便的共享父域内的定义。也因为子域中的重名定义会被父域所覆盖的缘由,父应用程序域拥有控制在子域中使用哪一个版本的定义的权力。
子应用程序域继承自父域
考虑如下情形:一个基于SWF的网站使用不一样的SWF文件来表明不一样的页面。主SWF负责加载这些子页面。每一个页面SWF基于一个相同的类库开发,具备类似的行为。好比都有一个PageTitle类来表示页面的标题文本。
假如在相同域下有另外一个SWF也用到这些相同的子页面,可是须要把子页面的标题文本变为不可选(假设原先的属性是可选择)。要实现这个例子里的目的,在PageTitle类中,咱们须要把TextField的selectable属性改成false。但这样改动的问题是会影响原先的SWF文件保持其原本的行为。
为了解决这个问题,咱们能够把每一个子页面都复制一份并从新编译。但这么作的话会占用更多的空间和网站流量。更好的办法是只编译第二个主SWF,把更新过的PageTitle类定义一块儿编译进去。而后在子页面在加载到子应用程序域的时候,这个类的定义就会被父域里的定义给覆盖。
原先全部子页面用的PageTitle类以下:
package { import flash.display.Sprite; import flash.text.TextField; public class PageTitle extends Sprite { private var title:TextField; public function PageTitle(titleText:String){ title = new TextField(); title.text = titleText; addChild(title); } } }
编译到第二个主文件里的更新版本的PageTitle类:
package { import flash.display.Sprite; import flash.text.TextField; public class PageTitle extends Sprite { private var title:TextField; public function PageTitle(titleText:String){ title = new TextField(); title.text = titleText; title.selectable = false; // changed addChild(title); } } }
把更新过的PageTitle类定义编译到新的主文件里面,并加载全部子页面到它们本身的子应用程序域中。
PageTitle; // 虽然没有直接用到PageTitle,但咱们能够包含一个引用,让它被一同编译进来 // 加载子页面到它们本身的子应用程序域中 // 加载的SWF将会用父域里的PageTitle定义取代掉它们自带的 function addChildPage(url:String):void { var context:LoaderContext = new LoaderContext(); var current:ApplicationDomain = ApplicationDomain.currentDomain; context.applicationDomain = new ApplicationDomain(current); var loader:Loader = new Loader(); addChild(loader); loader.load(new URLRequest(url), context); }
这种方法能够在不用从新编译子内容的前提下改变其中的类行为,这都是因为父应用程序域中的定义会覆盖子域中的定义的缘由。
注意在上面的例子也能够省略LoaderContext的使用,效果是同样的。
即使子SWF无需用做多重使用目的,更新主文件中的定义也比更新全部子文件的更加简单。实际上,子文件中甚至能够彻底不用包含这些定义,只依赖于主文件提供。这就是咱们将在 相同的域:运行时共享库 章节里将展开讨论的。
某些情形下,你可能不但愿加载的子SWF内容被父应用程序域里的定义继承关系所影响。由于有可能你甚至不知道父域中存在哪些定义。不论哪一种状况,最好都要避免主SWF和子SWF中的定义共享。在这种状况下,应该把子SWF的定义放到新的系统域的子域下。
系统域下的不一样子应用程序域
因为父SWF和子SWF的定义之间没有继承关系,因此这时候即便存在相同的定义也不会引发冲突,由于两者属于不一样的沙箱。
举个例子:好比你有个培训程序,经过加载外部SWF来表明不一样的培训模块。这个程序已经有些年头了,许多开发者开发了成百上千个培训模块。这些模块,甚至培训主程序自身都是基于不一样版本的基础代码库进行开发。因此主程序要保证本身使用的基础代码库不会对其余模块形成不兼容的状况。这就必须把这些培训模块加载到他们独立的系统域下的子域,而不是把他们加载到主应用程序域的子域下面。
trainingapplication.swf:
var moduleLoader:Loader = new Loader(); addChild(moduleLoader); // 把模块加载到系统域的子域下,与当前的应用程序域区分开 function loadModule(url:String):void { var context:LoaderContext = new LoaderContext(); context.applicationDomain = new ApplicationDomain(); moduleLoader.load(new URLRequest(url), context); }
不足的是,这种定义的划分方式还不是彻底隔离的。因为在同一个安全域下的内容都处于一个相同的系统域下,任何对系统域内定义的修改都将影响同一个安全域下的全部应用程序域。即便是将子SWF加载到一个单独的系统域的子域下,父SWF对系统域的更改仍是会对其形成影响。
咱们能够经过改动XML.prettyIndent
属性来验证这一点:无论处于应用程序域层级的哪一个SWF对系统域里的定义做出改变,都会影响到相同安全域下的全部文件。
parent.swf:
trace(XML.prettyIndent); // 2 XML.prettyIndent = 5; trace(XML.prettyIndent); // 5 var loader:Loader = new Loader(); var context:LoaderContext = new LoaderContext(); // 新建一个独立的应用程序域 context.applicationDomain = new ApplicationDomain(); var url:String = "child.swf"; loader.load(new URLRequest(url), context);
child.swf:
trace(XML.prettyIndent); // 5
因此最佳实践是对定义作的改动应该在使用后及时还原,这样能够避免对其余文件的影响。
var originalPrettyIndent:int = XML.prettyIndent; XML.prettyIndent = 5; trace(myXML.toXMLString()); XML.prettyIndent = originalPrettyIndent;
一样的,你也必须留心相似这样的值有可能在你的程序以外被人所改动。
把新增的定义增长到现有的应用程序域下多是应用程序域最大的用处。由于继承只能把父域内的定义对子域共享,而合并定义到相同的应用程序域内则能够对全部使用这个域的SWF共享,包括父级和子级。
父应用程序域包括了子SWF的定义
运行时共享库(RSLs)正是运用了这种机制。RSLs是能够在运行时被加载的独立的代码库。经过RSLs,其余SWF能够共用其中的代码而不须要编译到自身,从而排除了冗余,减少了文件量,也让代码更容易维护。咱们在主应用程序域中加载RSL,从而能够在整个程序中共享定义。
使用RSLs以前须要作些准备工做。首先,ActionScript编译器须要在发布SWF文件的时候知道哪些定义不须要被编译。
原生的Flash Player API定义就不须要编译。虽然每一个SWF都须要用到原生的定义(Array,XML,Sprite等),可是这些定义只存在于Flash Player的可执行文件中,不须要也不会被编译到SWF文件中。编译器使用一个叫作playerglobal.swc的特殊SWC(预先编译的SWF类库)来识别原生定义。它包含了原生定义的接口,包括定义的名字和数据类型等。编译器经过它来编译SWF,并且不会把这些定义编译到最终的SWF中。
编译器还能够引用其余相似playerglobal.swc同样的SWC库。这些库做为“外部”类库,其中包含的定义只是用于编译,不会包含到SWF内部。
这里不详细讨论在编辑工具中如何进行库连接的设置。不一样版本的编辑器的设置有些不一样,具体方法请参考Flash文档。
虽然咱们用SWCs来编译SWF,但实际上他们自己就是SWF文件,和其余被加载的SWF内容相似。在进行库编译的时候,同时生成了SWF和SWC文件。SWF用于运行时加载,而SWC在编译时用作外部库。
编译器使用SWCs共享库,SWF共享库在运行时加载
另外一个准备工做须要编写代码。使用外部库的时候,发布的SWF中不包含库中的定义。若是Flash Player尝试运行其中代码,就会产生核查错误,整个SWF基本上就瘫痪了。
Flash Player会在类第一次使用的时候校验其定义。若是应用程序域中不包括该定义,那么校验错误就会产生。
实际上缺乏定义产生的错误有两种。校验错误是两种之中最糟的,表示类没法正常工做的灾难性失败。另外一种是引用错误,当某种数据类型被引用可是却不可用的状况下发生。虽然缺失定义也会形成引用错误,但这种错误只会在已经通过核查的类内部打断代码执行的正常过程。
var instance:DoesNotExist; // VerifyError: Error #1014: Class DoesNotExist could not be found. // 当Flash Player校验包含该定义的类时发生校验错误 var instance:Object = new DoesNotExist(); // ReferenceError: Error #1065: Variable DoesNotExist is not defined. // 当代码执行到这一行的时候发生引用错误
主要的区别在于校验错误与类定义有关,而引用错误与代码执行相关。在类内部的代码要执行以前,必需要先经过校验。上面的例子中instance对象声明为Object类型,校验能够正常经过(只是在执行的时候就会遇到引用错误)。
Note: Strict Mode 注意:严格模式
外部库是引用定义而不需将其编译到SWF中的一种方法。另外一种方法是关闭严格模式,这将大大放宽了对变量使用的检查。对于类的使用来讲,你能够引用一个不存在的类而不会引发编译器报错。你不能直接把不存在的类用做变量类型(这样作会在运行时产生校验错误),可是你能够像上面的“引用错误”例子中那样去引用。在非严格模式下,编译器也许会检测不到一些可能发生的错误,因此一般不建议用这种模式。
使用了RSLs的SWF文件必须保证先加载好RSLs,才能使用这些外部定义。咱们应该在主应用程序开始执行以前用一个预加载器来加载RSLs。
下面演示了一个SWF加载包含Doughnut类的外部RSL的例子。虽然在SWF中直接引用了这个类,可是它倒是编译在外部库中,并经过SWC的方式来引用的。RSL在Doughnut类第一次使用以前就被加载进来,因此不会形成校验错误。
Doughnut.as (编译为 doughnutLibrary.swc 和 doughnutLibrary.swf):
package { import flash.display.Sprite; public class Doughnut extends Sprite { public function Doughnut(){ // draw a doughnut shape graphics.beginFill(0xFF99AA); graphics.drawCircle(0, 0, 50); graphics.drawCircle(0, 0, 25); } } }
ShapesMain.as (Shapes.swf的主类):
package { import flash.display.Sprite; public class ShapesMain extends Sprite { public function ShapesMain(){ // 虽然并无编译到Shapes.swf中, // 可是咱们经过doughnutLibrary.swc外部库 // 能够得到对Doughnut类的引用 var donut:Doughnut = new Doughnut(); donut.x = 100; donut.y = 100; addChild(donut); } } }
Shapes.swf (RSL loader):
var rslLoader:Loader = new Loader(); rslLoader.contentLoaderInfo.addEventListener(Event.INIT, rslInit); // 把RSL中的定义加载到当前应用程序域中 var context:LoaderContext = new LoaderContext(); context.applicationDomain = ApplicationDomain.currentDomain; var url:String = "doughnutLibrary.swf"; rslLoader.load(new URLRequest(url), context); function rslInit(event:Event):void { // 只有当RSL中的定义导入到当前应用程序域之后 // 咱们才能用其中的Doughnut定义经过ShapesMain类的校验 addChild(new ShapesMain()); }
在这个例子中,Shapes.swf是主程序,当RSL加载完毕后实例化主类ShapesMain。若是没有导入RSL中的定义,建立ShapesMain实例的时候就会由于在应用程序域中找不到对应的类而发生校验错误。
注意:Flex中的RSL
这里讨论的方法是最底层的方法,不该该用于Flex开发。Flex框架中有本身的一套RSLs处理机制,更多关于RSL在Flex中的应用,请参考 Flex Runtime Shared Libraries (Flex 4) 。
咱们能够用 Application.getDefinition 方法获取不在应用程序域内的定义,或者被父域覆盖的定义。这个方法返回应用程序域及其任意父域内的定义引用。在当前应用程序域内使用getDefinition方法的效果等同于全局函数 getDefinitionByName 。
咱们也能够经过SWF的 LoaderInfo.applicationDomain 来得到在 ApplicationDomain.currentDomain 之外的应用程序域。在下面的例子中咱们用Loader加载了一个SWF文件,而后在加载的那个应用程序域中提取com.example.Box类的定义。
try { var domain:ApplicationDomain = loader.contentLoaderInfo.applicationDomain; var boxClass:Class = domain.getDefinition("com.example.Box") as Class; var boxInstance:Object = new boxClass(); }catch(err:Error){ trace(err.message); }
以上的例子中包含了两个知识点。首先,getDefinition方法的返回值被显式的转换为Class类型,这是由于getDefinition默认返回的是Object类型,有可能表明了除了类类型之外的其余类型(函数,命名空间,接口)。其次,这个操做应该要放在try-catch函数体内,由于若是getDefinition查找定义失败将会抛出错误。或者你也能够在使用getDefinition以前用 ApplicationDomain.hasDefinition 方法检测是否可以成功找到某个定义。
用动态方式去获取的定义,而不是那些在当前应用程序域(及继承的程序域内)的定义,是不能用做变量类型的。就像RSL同样,在应用程序域内找不到的类定义会在校验的时候报错。因此上面的例子中boxInstance变量声明为Object类型而不是Box类型,就是由于Box类的定义在应用程序域内不存在。
有些时候可能会发生你引用的定义匹配到另外的应用程序域里的定义的交叉状况。这种状况将会产生以下强制转换类型错误:
TypeError: Error #1034: Type Coercion failed: cannot convert com.example::MyClass@51e1101 to com.example.MyClass.
你能够看到在不一样内存空间里的定义用@符号进行了区分。虽然它们内部的代码多是彻底相同的(或不一样),可是因为它们存在不一样的应用程序域(或安全域)内,因此它们是两个不一样的定义。
只有像Object那样的原生Flash Player定义才能够将位于不一样域(甚至是跨安全域的)的定义关联起来。实际上,大多数时候声明一个跨域的变量类型的时候都须要用Object类型。
虽然咱们能够用Object这种通用类型来解决定义冲突错误,实际上咱们更应该合理安排应用程序域的位置来消除这种不匹配的状况。
这篇教程包含了不少方面的信息。前半部分讨论了什么是安全域,以及它如何影响来自不一样域的内容。Flash Player用这种安全沙箱机制保护用户的数据。Flash开发者应该了解并合理利用这种限制。
第二部分讨论了应用程序域——另外一种用于在安全沙箱内划分ActionScript定义的沙箱类型。应用程序域的层级机制提供了在不一样的SWF直接共享和重用定义的方法。
在安全域和应用程序域的概念上有不少容易犯的错误。但愿这篇教程可以帮你对此有所准备。你不只应当了解他们的运做方式,还要知道如何正确运用它们以达成你想要的效果。
经过这篇文章的翻译,我才真正体会了翻译工做的难作。虽然平时看英文资料的速度还挺快,可是用中文详细复述一遍须要多花好几十倍的时间。在这篇教程的翻译中,我没有使用全文翻译等辅助工具,彻底靠手打,一边翻译一遍领会做者的意图。感受收获仍是比单纯看一遍要来得更多一些。
本文介绍的知识至关重要,特别是从AS2时代成长起来的开发者很容易就掉进文中提到的一些陷阱。彻底掌握这部分知识对设计模块架构,管理内存等方面都有很大的帮助。在此要再次感谢原做者为咱们带来这么精彩的教程。
译文转自:http://kevincao.com/2010/11/application-domains/
看完了应用程序域,若是还想再看点安全方面的,请看 教程:深刻理解Flash的安全沙箱 – Security Domains 。
» 原文连接地址:http://www.litefeel.com/application-domains/