?
? ? 想象下有这样的业务场景,我用flash实现了文件的上传,上传完之后,我们可能需要在页面上弹个提示框告诉用户上传结果。我们不可能把所有东西都放在flash里完成,因为那样flash的文件会加大,而且也不够灵活,因为flash是需要编译后才能跑的,所以我们最好把能用js实现的都剥离出来,让flash只做一些核心的功能。这样,我们就需要用as去调用js。同样的,如果js操作完了想通知到flash,就需要用js去调用flash中的as方法。此时可以看作as是js增强的一部分。
???? 不管是as调用js,还是js调用as,其实都围绕ExternalInterface。ExternalInterface 类是外部 API,这是一个在 ActionScript 和 Flash Player 容器之间实现直接通信的应用程序编程接口。当然插入flash的html标签中<param name='allowScriptAccess' value ='always' /> 不能为never,如果设置成never一下所有都是空谈了,一般情况下都设置成always,它也可以接受具体的domain或者IP。
???? ExternalInterface有两个提供相互调用的静态方法:
Example 1:
假如页面上有一个叫sayHi的js函数, 如下:
要在flash中调用这个函数,可以用两个办法:
运行结果,发现两个都没法执行,这是因为flashplayer只能看到页面上全局的javascript,所以要想能让flash拿到这个js函数,必须暴露出来。修改代码在运行
代码成功运行了,于是,我们可以总结出一点:提供给flash调用的js函数必须是全局的,获取寄宿在全局对象上的。
???? 不管是as调用js,还是js调用as,其实都围绕ExternalInterface。ExternalInterface 类是外部 API,这是一个在 ActionScript 和 Flash Player 容器之间实现直接通信的应用程序编程接口。当然插入flash的html标签中<param name='allowScriptAccess' value ='always' /> 不能为never,如果设置成never一下所有都是空谈了,一般情况下都设置成always,它也可以接受具体的domain或者IP。
???? ExternalInterface有两个提供相互调用的静态方法:
/** * @param functionName {String} * @param closure {Function} * @usage 将 ActionScript 方法注册为可提供外部js调用。 */ addCallback(functionName:String, closure:Function):void /** * @param functionName {String} * @param arguments {Function} (optional) * @usage 调用页面上的js。 */ call(functionName:String, ... arguments):*?
Example 1:
假如页面上有一个叫sayHi的js函数, 如下:
window.onload=function (){ function sayHi(){alert('Hello JS')} }?
要在flash中调用这个函数,可以用两个办法:
navigateToURL(new URLRequest( 'javascript:sayHi()' ), '_self'); //实际上打开一个新窗口,只不过url是javascript:sayHi(),这是土鳖方法 ExternalInterface.call( 'sayHi');// 用到了ExternalInterface类,这种方法也是adobe推荐的做法?
运行结果,发现两个都没法执行,这是因为flashplayer只能看到页面上全局的javascript,所以要想能让flash拿到这个js函数,必须暴露出来。修改代码在运行
window.onload=function (){ function sayHi(){alert('Hello JS')} window['sayHi'] = sayHi; }?
代码成功运行了,于是,我们可以总结出一点:提供给flash调用的js函数必须是全局的,获取寄宿在全局对象上的。
? ? ?有时候我们只用到了flash的功能,而没用到它的UI所以想把flash隐藏,比如做一个即时通讯的东西,只有通讯部分用到了flash, 当尝试把flash设置成display:none的时候,发现js与as根本无法相互调用,所以说如果需要隐藏flash,设置css是行不通的,可以设置它的宽高为1px来解决。
???? 如果是想在js中调用as方法,需要用ExternalInterface.addCallback注册as方法,让他暴露在flash实例上。比如flash中有这样的方法:
private function sayHi():void{ Alert.show('Hi As'); } External.addCallback('jsSayHi', sayHi);?
这样就可以在js中执行jsSayHi这个代理方法,它会去执行sayHi这个as方法。jsSayHi这个代理方法会寄宿在flash dom元素上,作为dom元素的方法。
window.onload=function (){ document.getElementById('flashId').sayHi(); }?
测试一下,还是发现了问题,有时候能正常运行,但有时候会抛出错误:Uncaught TypeError: Object #<HTMLObjectElement> has no method 'jsSayHi',这是因为即使当前页面onload了,但是flash中的代码初始化还没运行完,所以还没有把代理方法注册到flash dom元素上。(查看实例3-jscallas_b.html)
???? 那么,flash有没有类似domready的时间点呢?查看好多资料没看到,但是可以模拟一个。我们认识当flash能正常调用页面上的js时,flash能正常跟js进行交互。我们在页面上定义一个flashready函数,让flash回调。
function flashready(){ document.getElementById('flashId').jsSayHi(); alert('flash is ready'); }?
ExternalInterface.call( 'flashready' );?
? ? ?这样js对as的调用就能100%的成功了。
? ? ?对于静态资源往往会放在cdn上,比如有一台cdn域名是cdn.com,而主域名确是xxx.com,这样就出现了跨域的问题。浏览器的安全策略导致ajax不能正常的跨域请求,flash player的安全策略同样不允许flash跨域请求和调用。当遇到跨域时,flash player会抛出“安全沙箱冲突错误”。其实flash是支持跨域的,只不过要做一些设置。
???? 如果是跨域脚本执行,flash中有Secure.allowDomain(somedomain1, somedomain2)来允许制定的url请求,somedomain也可以是通配符"*",也就是不做源限制。如果flash的url是https的链接,则需要用allowInsecureDomain()来做沙箱桥,但如果当前html也是https,那不需要用它。尽量避免使用allowInsecureDomain会削弱https的安全性。但是在一些国产浏览器中,比如腾讯TT,遨游,360等浏览器中,经常会遇到第一次进来js能正常调用flash中的as,但是当刷新一次页面,发现调用不成功了。这是因为第二次访问的时候,flash被缓存到了本地,这些浏览器破坏了flash和浏览器的某种约定,所以导致他们不能相互调用。
???? 这个问题有两个做法:一、当flash文件很小时,用无缓存的方式解决,比如请求后面加随机数。二、延迟Flash的初始化功能。通过将Flash的ExternalInterface.addCallback初始化时间延后一些(比如500ms),就可以解决这个问题。
???? 顺便提一下,如果是flash中的http/socket等跨域请求,则需要一个叫crossdomain.xml的策略文件。这个文件放在服务器上,如果是跨域请求,在请求真实地址之前会去请求这个安全策略文件。
<cross-domain-policy> <allow-access-from domain=”*.xxx.com”/> <allow-access-from domain=”*.xxx.net”/> <allow-access-from domain=”*.xxxcdn.com”/> <allow-access-from domain=”*.allyes.org”/> </cross-domain-policy>?
???? 有人可能会考虑js与as之间频繁的相互调用会带来性能问题。简单的做了下测试
测试代码如下:
function flashready(){ var swf = document.getElementById('performance'), //方案1 //var swf = {'test':function(){return 1}}, //方案2 test = swf.test, i = 10000; var start = + new Date; while(i--){ test.call(swf); } alert(+ new Date - start); }?
flashready是一个全局的,提供给flash初始完回调的函数。注意代码中把获取flash dom放到一个变量里,因为dom操作本身开销大,如果不这么做会影响精确性。实验结果如下:
测试项目(测10次取平均值) | chrome 19 | firefox 9 | ie6 | ie7 | ie8 | ie9 |
js调用as 10000次耗时(方案1) | 1747ms | 1083ms | 360ms | 557ms | 485ms | 401ms |
js调用js 10000次耗时(方案2) | 1ms | 4ms | 15ms | 31ms | 15ms | 0ms |
观察结果,虽然说js与as执行的执行要比js与js之间慢很多很多,但是勉强还是能接受的,即使是最慢的chrome,平均调用一次也只需要0.17ms,但还是要尽量减少相互调用的次数,就像减少http请求一样。
综上所述,js与as的安全交互必须满足:
- <param name='allowScriptAccess' value ='always' />???
- flash不能隐藏(display:none)??
- 等被调用方初始化完成再去调用,as中可以用ExtercalInterface.call('flashready')来告知初始化完成
- 跨域执行,必须在flash中设置Secure.allowDomain或者Secure.allowInsecureDomain
?