AS3和JS的互相调用网上一搜能搜到很多,我确实也是这么做的。然而结果却并非令人满意,大部分都是抄子chm帮助手册,或者是叙述得不明白。于是我又手痒了,写篇详细易读的出来,连带分享一个防止缓存的小技巧。

在这里我要先描述一下功能的需求:写死swf文件,读取xml的配置信息,仅靠修改页面上的js代码来改动所需要的xml文件url,并防止缓存。

---

第一步,建立flash as3文件,之后它就不需要改动了,只要编写它的文档类并发布就行。这里我将.fla文件命名为Test.fla,文档类关联为Test.as。

首先我们来展示下AS3中调用JS的方法,这是最简单的事情,简单到只需一句话便可。

AS3的Test.as文档类:

package {
  import flash.display.Sprite;
  import flash.external.ExternalInterface;
  
  public class Test extends Sprite {
    public function Test() {
      callJavaScriptFunction();
    }
    
    private function callJavaScriptFunction():void {
      if(ExternalInterface.available) {
        ExternalInterface.call("sayHello", "army");
      }
    }
  }
}


发布页面中的JS代码:

function sayHello(name) {
  alert("hello, " + name);
}



as代码很简单,文档类Test继承Sprite,构造函数中执行定义的callJavaScriptFunction()方法。先要说明下ExternalInterface这个类,它在flash.external包下,官方的解释是“在 ActionScript 和 Flash Player 的容器之间实现直接通讯的应用程序编程接口,例如,含有 JavaScript 的 HTML 页。”,可以说是标准的通信接口,只要是和JS互相调用,都要用到它。


在callJavaScriptFunction()方法中,先是判断ExternalInterface.available属性,当外部接口可调用时,执行ExternalInterface类的call方法,它接收若干个参数。第一个是调用的js方法名,后面依次是传给所调用js方法的参数。在这里ExternalInterface.call("sayHello", "army")就相当于执行了js代码中的sayHello("army")方法。如你所见,页面中的确弹出了一个窗口,内容正是传过来的"army"。


需要注意的是,js代码段必须写在嵌入的swf段前面,因为这样才能确保执行之前js全部载入了,否则的话可能会出现无法执行的情况。


---


接下来我们来看看从JS中调用AS的方法。这里我先提个问题:由于放在网络上,swf文件的下载时间是个未知数,如何才能确保js调用as的时候swf已经加载完成了呢?


我所使用的是一个小技巧:依旧是js片段写在swf前面,先在swf里面调用js方法,然后所调用的js方法里再返回去调用as的方法。这样就可以确保swf加载完成并能顺利执行了,整个过程如下:


js片段首先被浏览器读取并加载 =》 浏览器读取swf文件直至完成 =》 swf调用js的方法 =》 被调用的js方法中去调用swf中的as方法。


AS3的Test.as文档类:

package {
  import flash.display.Sprite;
  import flash.external.ExternalInterface;
  import flash.text.TextField;
  
  public class Test extends Sprite {
    public function Test() {
      callJavaScriptFunction();
    }
    
    private function callJavaScriptFunction():void {
      if (ExternalInterface.available) {
        ExternalInterface.addCallback("callAsFunction", onCallBackHandler);
        ExternalInterface.call("callBackBridge");
      }
    }
    
    private function onCallBackHandler(s:String):void {
      var textField:TextField = new TextField();
      textField.text = s;
      addChild(textField);
    }
  }
}



发布页面中的JS代码:

function getSwfInstance(movieName) {
  if (navigator.appName.indexOf("Microsoft") != -1) {
    return window[movieName];
  } else {
    return document[movieName];
  }
}
function callBackBridge() {
  getSwfInstance("Test").callAsFunction("Hello, army!");
}


页面html代码:

<noscript>
  <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" width="725" height="350" id="VideoCenter" align="center">
  <param name="allowScriptAccess" value="sameDomain" />
  <param name="allowFullScreen" value="false" />
  <param name="movie" value="VideoCenter.swf" />
  <param name="quality" value="high" />
  <param name="bgcolor" value="#ffffff" />
  <embed src="VideoCenter.swf" quality="high" bgcolor="#ffffff" width="725" height="350" name="VideoCenter" align="center" allowScriptAccess="sameDomain" allowFullScreen="false" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" />
  </object>
</noscript>



为了使大家思路清晰,我们依照上面的过程一步步讲解:


1.js片段首先被浏览器读取并加载

2.浏览器读取swf文件直至完成


这两步没啥好说的,浏览器打开页面就会读取,不关我们的事情。


3.swf调用js的方法


我们看到Test.as中的构造函数依旧调用了callJavaScriptFunction()方法,不过这个方法已有所改变。先是判断ExternalInterface.available属性,然后addCallback()注册一个监听器,再调用js的callBackBridge()方法。

注意监听器必须先注册,这个监听器就是为了js回调as时而注册的,当回调发生时,这个监听器就会被激活,进而执行监听的方法。在这里我们监听js的callAsFunction()方法,并设定回调方法为as的onCallBackHandler()。


4.被调用的js方法中去调用swf中的as方法


由于swf里调用了js的callBackBridge()方法,因此会执行它。callBackBridge()方法内部先是用getSwfInstance()方法取得页面中的swf对象(根据浏览器不同而取得方法不同),然后调用取得对象的callAsFunction()方法,并传递了参数。

由于js中callAsFunction()方法被调用,因此as中注册的监听器监听到了方法名"callAsFunction",并把它转到回调函数onCallBackHandler()上。因此js中执行callAsFunction("Hello, army!")就相当于as中执行了onCallBackHandler("Hello, army!")一样。

结果不出所料,as的onCallBackHandler("Hello, army!")方法被执行,在swf上生成一个文本片段,片段内容正是传递过来的参数"Hello, army!"。


---


结尾的地方我要分享一个防止缓存的小技巧,也就是文章开头所说的功能需求。


通过as调用js方法,再让js回调as方法的这么一个办法,我们可以写死swf文件,通过修改页面中的js代码参数来让swf读取不同的xml配置文件。如果你看懂了上面的说明并亲自动手实践成功了,那么只需做小小修改便能完成这个需求。


接下来的问题是,假如我的xml文件名不想改动,怎么样才能防止缓存呢?


一种办法就是在as的urlloader中读取urlrequest时在xml文件末尾加上随机数,这是自动的办法;另一种就是手动修改js回调函数中的参数,在.xml文件后面加上唯一的随机参数,如:config.xml?1。每更新一次xml文件,就将那个参数+1,如此便能手动防止缓存。


值得一提的是,urlrequest在本地测试时对后面的参数会报错,说找不到文件,放在服务器上它便能够被解析了。