Flash有着天生非凡的动画和交互能力, 在RIA (富互联网应用程序)中有着天生的优势. 有些时候, flash客户端之间, 以及与其他客户程序如javascript (js)需要进行交互, 以提供更好的用户体验. 本文将对当前常用的多种flash客户端相关的交互方式进行归纳, 这些交互与服务器端无关.
Flash中使用的是actionscript (as)脚本, as脚本是一门基于ECMAScript的脚本语言, 在AVM(ActionScript Virtual Machine)上执行, 目前AVM分为AVM1和AVM2. 另一个著名的基于ECMAScript语言是javascript, 所以两者有很多共同特性. 不妨先来看一下as的发展历程:
as1: 2000年5月, 出现在flash player 5, 是一门弱类型, 基于prototype继承基制的语言, 这点和目前的js是一样的.
as2: 2003年9月, 出现在flash player 7, 支持编译时类型检查, 支持基于class继续的语法(实质上只是在已存在的基于prototype继承基制上增加了一层). 能够编译为as1的字节码, 所以与AS1是兼容的.
as3: 2006年6月, 出现在flash player 9, 随Adobe Flex 2一起发布. 它是AS的一次完全重构, 已经成为完全的面向对象的语言, 与as1/as2不再兼容, 运行于一个全新的虚拟机AVM2, 随之as1/as2运行的虚拟机被称为AVM1. as3的重新设计主要包括语言核心和flash player API两部分, 具体可查阅[url=http://www.adobe.com/devnet/actionscript/articles/actionscript3_overview.html]官方文档[/url]或[url=http://en.wikipedia.org/wiki/ActionScript]wikipedia[/url]. flash player 9同时包含AVM1和AVM2, 所以它支持三个as版本的flash, 只是会使用不同的虚拟机去运行它们.
下面切入主题, 分析一下flash客户端间的交互.
Flash中实现页面跳转
在flash中, actionscript 2 (as2)脚本可以通过getUrl发送一个url请求, 而actionscript 3 (as3)以flash.net.navigateToURL代替. navigateToURL在某些浏览器(比如firefox, ie 7等)下, 会被当弹出窗口拦截, 可以对特定的浏览器用ExternalInterface.call(window.open, url, “_blank”, “”)代替, 这实际上是在调用javascript函数.
另flash判断用户使用的浏览器, 也可以通过调用js实现, 如ExternalInterface.call(”function getBrowser(){return navigator.userAgent;}”).
Flash与Javascript交互
在flash player 8之前(不包括8), flash使用fscommand与js交互. 之后被ExternalInterface取代, 可见as2和as3都支持ExtenalInterface. ExternalInterface.call可以调用js代码, ExternalInterface.addCallBack可注册as2/as3函数供js调用. 在as3中, ExternalInterface位于包flash.external中.
ExternalInterface需要一个条件: “The ExternalInterface class requires the user’s web browser to support either ActiveX® or the NPRuntime API that is exposed by some browsers for plug-in scripting”.
注意在js中调用as函数, 取swf的引用时, ie6/7使用object的id, firefox使用embed的id.
Flash跨虚拟机的交互
Flash跨虚拟机, 指的得两个flash运行于两个不同的虚拟机(AVM1, AVM2), 如在不同的flash player/AIR中运行的flash, 或者如一个as3的swf加载了一个as2的swf, 这在flex 3.2之后可用mx.controls.SWFLoader实现, 等等. 跨虚拟机交互从as语言角度来说, 就是as3与as1/as2的交互. 这种交互主要有三种选择方案:
1. LocalConnection
先引用一段官方文档上有关LocalConnection的描述:
“Local connections enable this kind of communication between SWF files without the use of fscommand() or JavaScript. LocalConnection objects can communicate only among files that are running on the same client computer, but they can be running in different applications — for example, a file running in a browser and a SWF file running in Adobe AIR.
LocalConnection objects created in ActionScript 3.0 can communicate with LocalConnection objects created in ActionScript 1.0 or 2.0. The reverse is also true: LocalConnection objects created in ActionScript 1.0 or 2.0 can communicate with LocalConnection objects created in ActionScript 3.0. Flash Player handles this communication between LocalConnection objects of different versions automatically.”
LocalConnection提供了虚拟机间的通信通道, 实现在不同的客户端实现通信. 不过这个特性也可能成为缺点. 比如一个页面上多个flash使用LocalConnection进行通讯, 如果打开多个这样的页面, 如果标志LocalConnection的字符串相同, 这些页面可能会互相干扰. 解决的办法是为每一个LocalConnection提供唯一的标志字符串, 类似于id, 并且保证打开多个同样的页面, 这个标志字符串仍然是唯一的. 或许可以考虑把当前时间加入到标志字符串中保证唯一性.
另外, 有一个开源的库[url=http://www.gskinner.com/blog/archives/2007/07/swfbridge_easie.html]SWFBridge[/url]可以方便LocalConnection的开发.
2. 以javascript为桥梁通讯
当多个swf位于同一个页面, 或者, 一个swf加载了另一个swf, 并且, 它们所在环境支持使用ExternalInterface进行flash与js的交互, 就可以以js为桥梁实现flash客户端的交互.
同样, 有一个开源库[url=http://www.flashextensions.com/products/flashinterface.php]flashinterface[/url]可以方便该开发, 既可以调用, 也可以基于事件通信.
注意, 使用flashinterface时, 要在程序开始时先调用
FlashInterface.publish(this, true);
否则可能会出错. 如flex application里, 可以在Application initialize事件中调用.
3. 使用SharedObject传递数据
SharedObject类似于cookie, 可以保存flash客户端的数据, 自然也能用它来实现多个swf之间传递数据. [url=http://bbs.blueidea.com/viewthread.php?tid=2875507]如有人就用此种方式实现了一把.[/url]
不过这种方式, 如同cookie一样, 可能会受到安全机制的限制, 另外也不够灵活.
同一虚拟机中多个swf的交互
同一虚拟机多个swf的交互, 典型的例子如Flex 3.2使用SWFLoader加载了一个as3的flash, 此时, 两个swf都运行于AVM2. 这种情况下的交互支持上文提到的虚拟机间交互的所有方式, 不过更方便的是直接调用或使用事件.
如下面的例子, flex程序使用SWFLoader加载了一个as3 swf:
直接调用时, 可以传任何参数和返回值, 如
父swf调用子swf, 直接这样调用: MovieClip(myloader.content).doTest();
doTest是子swf的一个函数.
子swf调用父swf, 可以在SWFLoade的creationComplete响应中, 先把this传给子swf, 子swf就可以调父的方法了. [url=http://hi.baidu.com/longjiao81/blog/item/d6ff170fc700ff2f6159f3d6.html]如该博客中的例子(我试过可行).[/url]
用事件通信时, 可以直接在子swf中this.dispatchEvent(new flash.events.Event(”abcd”));
再在父中加入MovieClip(myloader.content).addEventListener(”abcd”, onAbcd);
或者, 在子swf中调用父的dispatchEvent, 在父中this.addEventListener.
Flash客户端间基本交互和解决方案主要就是这些, 它们各有所长, 具体选择哪一种就要看应用环境了.