深入理解Flash的沙箱 – Application Domains
这篇是《深入理解Flash Player的安全域(Security Domains)》的下文。
转载于:http://kevincao.com/2010/11/application-domains/
和安全域一样,不同安全沙箱下的SWF有着自己独立的类定义。这种在安全域下面进行划分和管理类定义(函数、接口和命名空间的定义也类似)的子域就是应用程序域。应用程序域只存在于安全域内,并且只能属于唯一的一个安全域。但是安全域可以包含多个应用程序域。
加载子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()才可以。
定义的继承和类继承有点类似,两者都是子级可以访问父级的定义,而反之则不行。
区别在于,应用程序域的继承不允许子级覆盖父级的定义。如果子域中包含有与父域一样的定义(指的是完全限定名称一致,包括包路径)。那么父域中的定义会取代掉子域。
编译器使用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 shapegraphics.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类的定义在应用程序域内不存在。
注:本人在作实验时,在父swf中
context.applicationDomain = new?ApplicationDomain(ApplicationDomain.currentDomain)
或context.applicationDomain = ApplicationDomain.currentDomain
var domain:ApplicationDomain = loader.contentLoaderInfo.applicationDomain;
var boxClass:Class = domain.getDefinition("com.example.Box") as Class;得到的始终是父swf中Box类的定义,
在父swf中,context.applicationDomain = new ApplicationDomain()时,才能得到子swf中Box类的定义。
?
有些时候可能会发生你引用的定义匹配到另外的应用程序域里的定义的交叉情况。这种情况将会产生如下强制转换类型错误:
TypeError: Error #1034: Type Coercion failed: cannot convertcom.example::MyClass@51e1101 to com.example.MyClass.
你可以看到在不同内存空间里的定义用@符号进行了区分。虽然它们内部的代码可能是完全相同的(或不同),但是由于它们存在不同的应用程序域(或安全域)内,所以它们是两个不同的定义。
只有像Object那样的原生Flash Player定义才可以将位于不同域(甚至是跨安全域的)的定义关联起来。实际上,大多数时候声明一个跨域的变量类型的时候都需要用Object类型。
虽然我们可以用Object这种通用类型来解决定义冲突错误,实际上我们更应该合理安排应用程序域的位置来消除这种不匹配的情况。
这篇教程包含了很多方面的信息。前半部分讨论了什么是安全域,以及它如何影响来自不同域的内容。Flash Player用这种安全沙箱机制保护用户的数据。Flash开发者应该了解并合理利用这种限制。
第二部分讨论了应用程序域——另一种用于在安全沙箱内划分ActionScript定义的沙箱类型。应用程序域的层级机制提供了在不同的SWF直接共享和重用定义的方法。
在安全域和应用程序域的概念上有很多容易犯的错误。希望这篇教程能够帮你对此有所准备。你不仅应当了解他们的运作方式,还要知道如何正确运用它们以达成你想要的效果。
?
通过这篇文章的翻译,我才真正体会了翻译工作的难做。虽然平时看英文资料的速度还挺快,但是用中文详细复述一遍需要多花好几十倍的时间。在这篇教程的翻译中,我没有使用全文翻译等辅助工具,完全靠手打,一边翻译一遍领会作者的意图。感觉收获还是比单纯看一遍要来得更多一些。
本文介绍的知识相当重要,特别是从AS2时代成长起来的开发者很容易就掉进文中提到的一些陷阱。完全掌握这部分知识对设计模块架构,管理内存等方面都有很大的帮助。在此要再次感谢原作者senocular为我们带来这么精彩的教程。
?
请大家尊重版权,转载请注明出处。