外部 API 示例:在 ActionScript 和 Web 浏览器中的 JavaScript 之间进行通信

Flash Player 9 和更高版本,Adobe AIR 1.0 和更高版本

本范例应用程序演示实现 ActionScript 与 Web 浏览器中的 JavaScript 间的通信的正确方法,范例的环境为一个允许用户相互对话的即时消息应用程序(因此该应用程序的名称为:Introvert IM)。它使用外部 API 在网页中的 HTML 表单与 SWF 接口之间发送消息。本示例说明的方法包括:

  • 通过验证浏览器在建立通信之前已准备就绪,正确地启动通信

  • 检查容器是否支持外部 API

  • 从 ActionScript 调用 JavaScript 函数,传递参数,并接收响应中的值

  • 使 ActionScript 方法可由 JavaScript 调用,并执行这些调用

若要获取此范例的应用程序文件,请参阅 www.adobe.com/go/learn_programmingAS3samples_flash_cn。Introvert IM 应用程序文件位于 Samples/IntrovertIM_HTML 文件夹中。该应用程序包含以下文件:

文件

说明

IntrovertIMApp.fla

IntrovertIMApp.mxml

Flash 或 Flex 的主应用程序文件(分别为 FLA 和 MXML)。

com/example/programmingas3/introvertIM/IMManager.as

建立和管理 ActionScript 与容器间的通信的类。

com/example/programmingas3/introvertIM/IMMessageEvent.as

自定义事件类型,从容器接收到消息时由 IMManager 类调度。

com/example/programmingas3/introvertIM/IMStatus.as

枚举,其值表示可在该应用程序中选择的不同“可用性”状态值。

html-flash/IntrovertIMApp.html

html-template/index.template.html

Flash 应用程序的 HTML 页 (html-flash/IntrovertIMApp.html),或用于为 Adobe Flex 应用程序创建容器 HTML 页的模板 (html-template/index.template.html)。此文件包含组成该应用程序的容器部分的所有 JavaScript 函数。

准备 ActionScript 与浏览器间的通信

外部 API 最常见的用途之一就是允许 ActionScript 应用程序与 Web 浏览器进行通信。使用外部 API 时,ActionScript 方法可以调用使用 JavaScript 编写的代码,反之亦然。由于浏览器的复杂性及其内部呈示页的方式,因此根本无法保证 SWF 文档能够在 HTML 页中的第一个 JavaScript 运行之前注册它的回调。出于这个原因,在从 JavaScript 调用 SWF 文档中的函数之前,SWF 文档应总是调用 HTML 页,以通知它 SWF 文档已准备好接受连接。

例如,通过 IMManager 类执行的一系列步骤,Introvert IM 可确定浏览器是否做好了通信的准备,并为通信准备 SWF 文件。第一个步骤(确定浏览器是否已做好通信准备)是在 IMManager 构造函数中执行的,如下所示:

public function IMManager(initialStatus:IMStatus) 
{ 
    _status = initialStatus; 
 
    // Check if the container is able to use the external API. 
    if (ExternalInterface.available) 
    { 
        try 
        { 
            // This calls the isContainerReady() method, which in turn calls 
            // the container to see if Flash Player has loaded and the container 
            // is ready to receive calls from the SWF. 
            var containerReady:Boolean = isContainerReady(); 
            if (containerReady) 
            { 
                // If the container is ready, register the SWF's functions. 
                setupCallbacks(); 
            } 
            else 
            { 
                // If the container is not ready, set up a Timer to call the 
                // container at 100ms intervals. Once the container responds that 
                // it's ready, the timer will be stopped. 
                var readyTimer:Timer = new Timer(100); 
                readyTimer.addEventListener(TimerEvent.TIMER, timerHandler); 
                readyTimer.start(); 
            } 
        } 
        ... 
    } 
    else 
    { 
        trace("External interface is not available for this container."); 
    } 
}

首先,代码使用 ExternalInterface.available 属性检查外部 API 是否还可在当前容器中使用。如果可用,它将开始建立通信的过程。由于尝试与外部应用程序通信会产生安全异常及其他错误,因此将代码包括在 try 块中(为了简便起见,列表中省略了相应的 catch 块)。

然后,代码调用 isContainerReady() 方法,如下所示:

private function isContainerReady():Boolean 
{ 
    var result:Boolean = ExternalInterface.call("isReady"); 
    return result; 
}

isContainerReady() 方法又使用 ExternalInterface.call() 方法来调用 JavaScript 函数 isReady(),如下所示:

<script language="JavaScript"> 
<!-- 
// ------- Private vars ------- 
var jsReady = false; 
... 
// ------- functions called by ActionScript ------- 
// called to check if the page has initialized and JavaScript is available 
function isReady() 
{ 
    return jsReady; 
} 
... 
// called by the onload event of the <body> tag 
function pageInit() 
{ 
    // Record that JavaScript is ready to go. 
    jsReady = true; 
} 
... 
//--> 
</script>

isReady() 函数仅返回 jsReady 变量的值。该变量最初为 false;一旦触发了网页的 onload 事件,该变量的值即更改为 true。也就是说,如果 ActionScript 在加载页之前调用 isReady() 函数,则 JavaScript 对 ExternalInterface.call("isReady") 返回 false,因此使得 ActionScript isContainerReady() 方法返回 false。加载页之后,JavaScript isReady() 函数将返回 true,从而使得 ActionScript isContainerReady() 方法也返回 true

回到 IMManager 构造函数中,根据容器的准备情况,将发生两种情况之一。如果 isContainerReady() 返回 true,则代码调用 setupCallbacks() 方法,该方法将完成与 JavaScript 建立通信的过程。另一方面,如果 isContainerReady() 返回 false,实际上会使该过程陷入停顿状态。创建 Timer 对象,并指示其每隔 100 毫秒调用 timerHandler() 方法一次,如下所示:

private function timerHandler(event:TimerEvent):void 
{ 
    // Check if the container is now ready. 
    var isReady:Boolean = isContainerReady(); 
    if (isReady) 
    { 
        // If the container has become ready, we don't need to check anymore, 
        // so stop the timer. 
        Timer(event.target).stop(); 
        // Set up the ActionScript methods that will be available to be 
        // called by the container. 
        setupCallbacks(); 
    } 
}

每次调用 timerHandler() 方法时,它都会再次检查 isContainerReady() 方法的结果。初始化容器后,该方法返回 true。然后,代码停止 Timer,并调用 setupCallbacks() 方法来完成与浏览器建立通信的过程。

向 JavaScript 公开 ActionScript 方法

如上例所示,一旦代码确定浏览器已就绪,就将调用 setupCallbacks() 方法。此方法准备 ActionScript 以接收来自 JavaScript 的调用,如下所示:

private function setupCallbacks():void 
{ 
    // Register the SWF client functions with the container 
    ExternalInterface.addCallback("newMessage", newMessage); 
    ExternalInterface.addCallback("getStatus", getStatus); 
    // Notify the container that the SWF is ready to be called. 
    ExternalInterface.call("setSWFIsReady"); 
}

setCallBacks() 方法通过调用 ExternalInterface.addCallback() 来注册两个可从 JavaScript 调用的方法,从而完成与容器进行通信的准备任务。在此代码中,第一个参数与 ActionScript 中方法的名称是相同的,该参数就是 JavaScript 所知道的方法的名称("newMessage""getStatus")。(在本例中,使用不同的名称毫无益处,因此为了简便起见,重复使用相同的名称。)最后,使用 ExternalInterface.call() 方法来调用 JavaScript 函数 setSWFIsReady(),该函数通知容器 ActionScript 函数已注册。

从 ActionScript 到浏览器的通信

Introvert IM 应用程序说明一系列在容器页中调用 JavaScript 函数的示例。在最简单的情况下(setupCallbacks() 方法的示例),调用 JavaScript 函数 setSWFIsReady() 时不传递任何参数也不接收返回值:

ExternalInterface.call("setSWFIsReady");

isContainerReady() 方法的另一个示例中,ActionScript 调用 isReady() 函数,并接受响应中的一个布尔值:

var result:Boolean = ExternalInterface.call("isReady");

还可以使用外部 API 将参数传递给 JavaScript 函数。以 IMManager 类的 sendMessage() 方法为例,该方法在用户向他(或她)的“对话伙伴”发送新消息时调用:

public function sendMessage(message:String):void 
{ 
    ExternalInterface.call("newMessage", message); 
}

再次使用 ExternalInterface.call() 调用指定的 JavaScript 函数,以通知浏览器有新的消息。另外,消息本身也作为另一个参数传递给 ExternalInterface.call(),从而作为参数传递给 JavaScript 函数 newMessage()

从 JavaScript 调用 ActionScript 代码

通信必须是双向的,Introvert IM 应用程序也不例外。不仅 Flash Player IM 客户端调用 JavaScript 来发送消息,而且 HTML 表单也调用 JavaScript 代码来向 SWF 文件发送消息和请求信息。例如,当 SWF 文件通知容器它已建立了联系并做好了通信的准备时,浏览器执行的第一个操作就是调用 IMManager 类的 getStatus() 方法,以检索 SWF IM 客户端的初始用户可用性状态。此操作是在网页中(在 updateStatus() 函数中)执行的,如下所示:

<script language="JavaScript"> 
... 
function updateStatus() 
{ 
    if (swfReady) 
    { 
        var currentStatus = getSWF("IntrovertIMApp").getStatus(); 
        document.forms["imForm"].status.value = currentStatus; 
    } 
} 
... 
</script>

代码检查 swfReady 变量的值,该变量跟踪 SWF 文件是否已经通知浏览器,它已在 ExternalInterface 类中注册了其方法。如果 SWF 文件已准备好接收通信,则下一行代码 (var currentStatus = ...) 实际调用 IMManager 类中的 getStatus() 方法。该行代码中执行三个操作:

  • 调用 getSWF() JavaScript 函数,返回对表示 SWF 文件的 JavaScript 对象的引用。当 HTML 页中存在多个 SWF 文件时,传递给 getSWF() 的参数决定了返回哪一个浏览器对象。传递给该参数的值必须与 object 标签的 id 属性以及用于包括 SWF 文件的 embed 标签的 name 属性相匹配。

  • 在使用对 SWF 文件的引用时,getStatus() 方法就像是 SWF 对象的方法一样被调用。在这种情况下,使用函数名“getStatus”,因为它是使用 ExternalInterface.addCallback() 注册的 ActionScript 函数名。

  • getStatus() ActionScript 方法返回一个值,该值被赋给 currentStatus 变量,该变量作为 status 文本字段的内容(value 属性)被赋值。

注: 如果您仔细查看这段代码,可能会注意到在 updateStatus() 函数的源代码中,调用 getSWF() 函数的代码行实际编写如下:var currentStatus = getSWF("${application}").getStatus();${application} 文本是 HTML 页面模板中的占位符;在 Adobe Flash Builder 为应用程序生成实际的 HTML 页面时,此占位符文本会由用作 object 标签的 id 属性和 embed 标签的 name 属性的相同文本所替换(在本示例中为 IntrovertIMApp)。这是 getSWF() 函数所需的值。

sendMessage() JavaScript 函数说明如何将参数传递给 ActionScript 函数。(sendMessage() 是用户按 HTML 页上的“发送”按钮时调用的函数。)

<script language="JavaScript"> 
... 
function sendMessage(message) 
{ 
    if (swfReady) 
    { 
        ... 
        getSWF("IntrovertIMApp").newMessage(message); 
    } 
} 
... 
</script>

newMessage() ActionScript 方法需要使用一个参数,因此将 JavaScript message 变量作为 JavaScript 代码中 newMessage() 方法调用中的参数传递给 ActionScript。

检测浏览器类型

由于各种浏览器在访问内容的方式上存在差异,所以必须总是使用 JavaScript 来检测用户正在运行哪种浏览器,并根据特定于浏览器的语法使用窗口对象或文本对象访问影片,如本例中的 getSWF() JavaScript 函数所示:

<script language="JavaScript"> 
... 
function getSWF(movieName) 
{ 
    if (navigator.appName.indexOf("Microsoft") != -1) 
    { 
        return window[movieName]; 
    } 
    else 
    { 
        return document[movieName]; 
    } 
} 
... 
</script>

如果脚本不检测用户的浏览器类型,则在 HTML 容器中播放 SWF 文件时,用户可能会遇到意外情况。