AIR 应用程序的调用和终止

Adobe AIR 1.0 和更高版本

本节讨论几种对已安装的 Adobe® AIR® 应用程序进行调用的方法,以及关闭运行中的应用程序的选项和注意事项。

注: NativeApplication、InvokeEvent 和 BrowserInvokeEvent 对象只可用于 AIR 应用程序沙箱中运行的 SWF 内容。在 Flash Player 运行时、浏览器、独立播放器(放映文件)或应用程序沙箱之外的 AIR 应用程序中运行的 SWF 内容无法访问这些类。

有关调用和终止 AIR 应用程序的快速介绍和代码示例,请参阅 Adobe Developer Connection 上的以下快速入门文章:

应用程序调用

在用户(或操作系统)执行以下操作时,将调用 AIR 应用程序:

  • 从桌面解释程序启动该应用程序。

  • 使用该应用程序作为命令行解释程序中的命令。

  • 打开某类型文件而该应用程序是此类型文件的默认打开程序。

  • (Mac OS X) 单击停靠任务栏中的该应用程序图标(无论应用程序当前是否正在运行)。

  • 选择从安装程序启动该应用程序(在新安装过程结束时,或者在双击已安装应用程序的 AIR 文件之后)。

  • 当已安装版本指示其自身正在处理应用程序更新(通过在应用程序描述符文件中加入 <customUpdateUI>true</customUpdateUI> 声明)时,开始更新 AIR 应用程序。

  • (iOS) 从 Apple 推送通知服务 (APNs) 接收通知。

  • 通过 URL 调用应用程序。

  • 访问承载了将调用 com.adobe.air.AIR launchApplication() 方法(该方法可为 AIR 应用程序指定识别信息)的 Flash 标志或应用程序的网页。(要使浏览器调用成功,应用程序描述符还必须包含 <allowBrowserInvocation>true</allowBrowserInvocation> 声明。)

每当调用 AIR 应用程序时,AIR 都会通过单一 NativeApplication 对象调度类型为 invoke 的 InvokeEvent 对象。若要给应用程序留出时间来初始化自身并注册事件侦听器,将对 invoke 事件进行排队而非将其丢弃。一旦侦听器已注册,就会传送所有排队的事件。

注: 使用浏览器调用功能来调用某个应用程序时,如果该应用程序尚未运行,则 NativeApplication 对象将仅调度一个 invoke 事件。

若要接收 invoke 事件,请调用 NativeApplication 对象 (NativeApplication.nativeApplication)addEventListener() 方法。当某个事件侦听器为 invoke 事件进行注册后,该事件侦听器还会接收到在注册前发生的所有 invoke 事件。在对 addEventListener() 的调用返回后不久,将以短时间间隔一次调度一个排队的 invoke 事件。如果在此过程中发生了新的 invoke 事件,则可能会在一个或多个排队的事件之前调度该事件。通过该事件队列可处理在初始化代码执行之前发生的任何 invoke 事件。请记住,如果在执行后期(在应用程序初始化之后)添加一个事件侦听器,该事件侦听器仍将会接收自应用程序启动以来发生的所有 invoke 事件。

仅启动 AIR 应用程序的一个实例。当再次调用某个已经运行的应用程序时,AIR 将向该正在运行的实例调度一个新的 invoke 事件。AIR 应用程序负责响应 invoke 事件并采取适当的动作(例如,打开一个新的文档窗口)。

InvokeEvent 对象包含任何传递给该应用程序的参数,以及已从中调用该应用程序的目录。如果该应用程序是由于文件类型关联而被调用,则文件的完整路径将包含在命令行参数中。同样,如果该应用程序是由于某个应用程序升级而被调用,则会提供升级 AIR 文件的完整路径。

当在一次操作中打开多个文件时,在 Mac OS X 中将调度一个 InvokeEvent 对象。每个文件都包括在 arguments 数组中。 在 Windows 和 Linux 中将为每个文件调度一个单独的 InvokeEvent 对象。

应用程序可通过以下方法处理 invoke 事件:即向其 NativeApplication 对象注册侦听器,

NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, onInvokeEvent); 

然后定义事件侦听器:

var arguments:Array; 
var currentDir:File; 
public function onInvokeEvent(invocation:InvokeEvent):void { 
    arguments = invocation.arguments; 
    currentDir = invocation.currentDirectory; 
} 

捕获命令行参数

与 AIR 应用程序调用关联的命令行参数是在由 NativeApplication 对象调度的 InvokeEvent 对象中进行传送的。InvokeEvent arguments 属性包含在调用 AIR 应用程序时由操作系统传递的参数数组。如果这些参数包含相对文件路径,则通常可以使用 currentDirectory 属性来解析路径。

除非用双引号引起来,否则传递给 AIR 程序的参数会视为以空白分隔的字符串:

参数

Array

tick tock

{tick,tock}

tick "tick tock"

{tick,tick tock}

"tick" “tock”

{tick,tock}

\"tick\" \"tock\"

{"tick","tock"}

InvokeEvent 对象的 currentDirectory 属性包含一个表示应用程序启动时所在目录的 File 对象。

如果调用某应用程序是由于要打开该应用程序所注册类型的文件,则该文件的本机路径将以字符串的形式包含在命令行参数中。(您的应用程序负责打开该文件或对其执行预定操作。)同样,如果已对应用程序进行编程使其能实现自我更新(而非依赖于标准 AIR 更新用户界面),则当用户双击某个 AIR 文件(该文件包含具有匹配应用程序 ID 的应用程序)时,将会包含该 AIR 文件的本机路径。

使用 currentDirectory File 对象的 resolve() 方法可访问该文件:

if((invokeEvent.currentDirectory != null)&&(invokeEvent.arguments.length > 0)){ 
    dir = invokeEvent.currentDirectory; 
    fileToOpen = dir.resolvePath(invokeEvent.arguments[0]); 
}

此外,还应验证参数是否的确是文件的路径。

示例:调用事件日志

下面的示例演示如何为 invoke 事件注册侦听器以及如何处理该事件。该示例会记录所有接收到的调用事件并显示当前目录和命令行参数。

ActionScript 示例

package  
{ 
    import flash.display.Sprite; 
    import flash.events.InvokeEvent; 
    import flash.desktop.NativeApplication; 
    import flash.text.TextField; 
         
    public class InvokeEventLogExample extends Sprite 
    { 
        public var log:TextField; 
         
        public function InvokeEventLogExample() 
        { 
            log = new TextField(); 
            log.x = 15; 
            log.y = 15; 
            log.width = 520; 
            log.height = 370; 
            log.background = true; 
             
            addChild(log); 
 
            NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, onInvoke); 
        } 
             
        public function onInvoke(invokeEvent:InvokeEvent):void 
        { 
            var now:String = new Date().toTimeString(); 
            logEvent("Invoke event received: " + now); 
                     
            if (invokeEvent.currentDirectory != null) 
            { 
                logEvent("Current directory=" + invokeEvent.currentDirectory.nativePath); 
            }  
            else  
            { 
                logEvent("--no directory information available--"); 
            } 
                     
            if (invokeEvent.arguments.length > 0) 
            { 
                logEvent("Arguments: " + invokeEvent.arguments.toString()); 
            }  
            else  
            { 
                logEvent("--no arguments--"); 
            } 
        } 
                 
        public function logEvent(entry:String):void  
        { 
            log.appendText(entry + "\n"); 
            trace(entry); 
        } 
    } 
} 

Flex 示例

<?xml version="1.0" encoding="utf-8"?> 
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" 
    invoke="onInvoke(event)" title="Invocation Event Log"> 
    <mx:Script> 
    <![CDATA[ 
    import flash.events.InvokeEvent; 
    import flash.desktop.NativeApplication; 
 
    public function onInvoke(invokeEvent:InvokeEvent):void { 
        var now:String = new Date().toTimeString(); 
        logEvent("Invoke event received: " + now); 
                 
        if (invokeEvent.currentDirectory != null){ 
            logEvent("Current directory=" + invokeEvent.currentDirectory.nativePath); 
        } else { 
            logEvent("--no directory information available--"); 
        } 
                 
        if (invokeEvent.arguments.length > 0){ 
            logEvent("Arguments: " + invokeEvent.arguments.toString()); 
        } else { 
            logEvent("--no arguments--"); 
        } 
    } 
             
    public function logEvent(entry:String):void { 
        log.text += entry + "\n"; 
        trace(entry); 
    } 
    ]]> 
    </mx:Script> 
    <mx:TextArea id="log" width="100%" height="100%" editable="false" 
        valueCommit="log.verticalScrollPosition=log.textHeight;"/> 
</mx:WindowedApplication>

用户登录时调用 AIR 应用程序

通过将 NativeApplication startAtLogin 属性设置为 true,可以将 AIR 应用程序设置为在当前用户登录时自动启动。设置后,每当该用户登录时该应用程序都将自动启动。除非该设置更改为 false、用户通过操作系统手动更改该设置或者卸载了该应用程序,否则该应用程序将始终在登录时启动。登录时启动是一种运行时设置。该设置仅适用于当前用户。必须安装该应用程序以将 startAtLogin 属性成功设置为 true。如果该属性是在未安装应用程序时设置的,则会引发错误(例如,通过 ADL 启动该应用程序时)。

注: 该应用程序在计算机系统启动时不会启动。它会在用户登录时启动。

若要确定应用程序是自动启动还是用户操作的结果,可以检查 InvokeEvent 对象的 reason 属性。如果该属性的值等于 InvokeEventReason.LOGIN,则应用程序为自动启动。对于其他调用路径,reason 属性设置如下:

  • InvokeEventReason.NOTIFICATION(仅限 iOS)— 已通过 APNs 调用应用程序。有关 APNs 的详细信息,请参阅使用推送通知

  • InvokeEventReason.OPEN_URL — 该应用程序已被其他应用程序或系统调用。

  • InvokeEventReason.Standard — 所有其他情况。

要访问 reason 属性,您的应用程序必须面向 AIR 1.5.1 或更高版本(通过在应用程序描述符文件中设置正确命名空间值)。

以下简化的应用程序利用 InvokeEvent reason 属性来确定在发生 invoke 事件时如何响应。如果 reason 属性为“login”,则应用程序仍在后台运行。否则,它将显示主要应用程序。采用此方式的应用程序通常在登录时启动(从而可以执行后台处理或事件监控),并打开窗口以响应用户触发的 invoke 事件。

package { 
    import flash.desktop.InvokeEventReason; 
    import flash.desktop.NativeApplication; 
    import flash.display.Sprite; 
    import flash.events.InvokeEvent; 
 
    public class StartAtLogin extends Sprite 
    { 
        public function StartAtLogin() 
        { 
            try 
            { 
                NativeApplication.nativeApplication.startAtLogin = true; 
            } 
            catch ( e:Error ) 
            { 
                trace( "Cannot set startAtLogin:" + e.message ); 
            } 
             
            NativeApplication.nativeApplication.addEventListener( InvokeEvent.INVOKE, onInvoke ); 
        } 
                 
        private function onInvoke( event:InvokeEvent ):void 
        { 
            if( event.reason == InvokeEventReason.LOGIN ) 
            { 
                //do background processing... 
                trace( "Running in background..." ); 
            }             
            else 
            { 
                this.stage.nativeWindow.activate(); 
            } 
        } 
    } 
}
注: 若要查看行为中的差异,请打包并安装该应用程序。只能为已安装的应用程序设置 startAtLogin 属性。

从浏览器调用 AIR 应用程序

使用浏览器调用功能,网站可启动要从浏览器启动的已安装 AIR 应用程序。仅在应用程序描述符文件将 allowBrowserInvocation 设置为 true 时,才允许浏览器调用:

<allowBrowserInvocation>true</allowBrowserInvocation>

当该应用程序通过浏览器调用时,该应用程序的 NativeApplication 对象将会调度 BrowserInvokeEvent 对象。

若要接收 BrowserInvokeEvent 事件,请在 AIR 应用程序中调用 NativeApplication 对象 (NativeApplication.nativeApplication) 的 addEventListener() 方法。当某个事件侦听器针对 BrowserInvokeEvent 事件进行注册时,该事件侦听器还将接收在注册前发生的所有 BrowserInvokeEvent 事件。在对 addEventListener() 的调用返回后将调度这些事件,但并不一定会在注册后可能接收到的其他 BrowserInvokeEvent 事件之前调度。这样就可处理在初始化代码执行之前(如从浏览器首次调用应用程序时)已发生的 BrowserInvokeEvent 事件。请记住,如果在执行后期(在应用程序初始化之后)添加一个事件侦听器,它仍然会接收到自应用程序启动以来发生的所有 BrowserInvokeEvent 事件。

BrowserInvokeEvent 对象包含以下属性:

属性

说明

arguments

要传递给应用程序的参数(字符串)数组。

isHTTPS

浏览器中的内容是否使用 https URL 方案。如果是,则为 (true,否则为 (false)。

isUserEvent

浏览器调用是否生成用户事件(如鼠标单击)。在 AIR 1.0 中,它始终设置为 true;AIR 需要与浏览器调用功能有关的用户事件。

sandboxType

浏览器中内容的沙箱类型。定义的有效值与可用于 Security.sandboxType 属性的值相同,可以是以下值之一:

  • Security.APPLICATION — 内容位于应用程序安全沙箱中。

  • Security.LOCAL_TRUSTED — 内容位于只能与本地文件系统内容交互的安全沙箱中。

  • Security.LOCAL_WITH_FILE — 内容位于只能与本地文件系统内容交互的安全沙箱中。

  • Security.LOCAL_WITH_NETWORK — 内容位于只能与远程内容交互的安全沙箱中。

  • Security.REMOTE — 内容位于远程(网络)域中。

securityDomain

浏览器中的内容的安全域,如"www.adobe.com""www.example.org"。仅对于远程安全沙箱中的内容(来自网络域的内容)设置此属性,而不对位于本地或应用程序安全沙箱中的内容设置此属性。

如果使用浏览器调用功能,请务必考虑安全性问题。网站启动 AIR 应用程序时,它可通过 BrowserInvokeEvent 对象的 arguments 属性发送数据。请在任何敏感操作(例如文件或代码加载 API)中谨慎使用此数据。风险级别取决于应用程序处理数据的方式。如果只希望某个特定网站调用该应用程序,则该应用程序应检查 BrowserInvokeEvent 对象的 securityDomain 属性。也可以要求调用该应用程序的网站使用 HTTPS,这样就可通过检查 BrowserInvokeEvent 对象的 isHTTPS 属性进行验证。

应用程序应验证传入的数据。例如,如果某个应用程序要求传入指向某个特定域的 URL,则该应用程序应验证 URL 确实指向该域。这样就能够阻止攻击者欺骗该应用程序向其发送敏感数据。

任何应用程序都不应使用可能指向本地资源的 BrowserInvokeEvent 参数。例如,应用程序不应基于从浏览器传递的路径创建 File 对象。如果从浏览器传递的将是远程路径,则该应用程序应确保路径不使用 file:// 协议替代远程协议。

应用程序终止

终止应用程序最快的方法是调用 NativeApplication exit() 方法。在应用程序没有要保存的数据或没有要清理的外部资源时,此方法非常适用。调用 exit() 将关闭所有窗口,然后终止该应用程序。但是,若要允许窗口或应用程序的其他组件中断该终止进程(也许要保存重要数据),请在调用 exit() 之前调度适当的警告事件。

另一种妥善关闭应用程序的方法是提供单一执行路径,而不考虑关闭进程的启动方式。用户(或操作系统)可以通过以下方式触发应用程序终止:

  • NativeApplication.nativeApplication.autoExittrue 的情况下关闭最后一个应用程序窗口。

  • 从操作系统中选择应用程序退出命令;例如,用户从默认菜单中选择退出应用程序命令。(这种情况仅适用于 Mac OS;Windows 和 Linux 并不通过系统镶边提供应用程序退出命令。)

  • 关闭计算机。

当采用上述方式之一通过操作系统执行退出命令时,NativeApplication 将调度 exiting 事件。如果没有侦听器取消 exiting 事件,则任何打开的窗口都将关闭。每个窗口都会先调度一个 closing 事件,然后调度一个 close 事件。如果任何窗口取消 closing 事件,则关闭进程将会停止。

如果需要考虑应用程序的窗口关闭顺序,则可侦听来自 NativeApplication 的 exiting 事件并自行决定以适当的顺序关闭窗口。例如,如果您拥有带有工具调色板的文档窗口,则可能需要运用此方法。如果系统关闭了调色板而用户却决定取消退出命令以保存某些数据,这可能会带来不便,甚至更糟糕的情形。在 Windows 中,收到 exiting 事件的唯一时间是在关闭最后一个窗口之后(当 NativeApplication 对象的 autoExit 属性设置为 true 时)。

若要在所有平台上提供一致的行为,而不考虑退出顺序是通过操作系统镶边、菜单命令还是通过应用程序逻辑启动的,请遵守用于退出应用程序的以下良好做法:

  1. 始终先通过 NativeApplication 对象调度 exiting 事件,然后再调用应用程序代码中的 exit() 并检查应用程序的其他组件是否没有取消该事件。

    public function applicationExit():void { 
        var exitingEvent:Event = new Event(Event.EXITING, false, true); 
        NativeApplication.nativeApplication.dispatchEvent(exitingEvent); 
        if (!exitingEvent.isDefaultPrevented()) { 
            NativeApplication.nativeApplication.exit(); 
        } 
    } 
  2. 侦听来自 NativeApplication.nativeApplication 对象的应用程序 exiting 事件,并在处理函数中关闭任何窗口(首先调度 closing 事件)。在所有窗口都已关闭后执行任何所需的清理任务,例如保存应用程序数据或删除临时文件。在清理期间仅使用同步方法以确保在应用程序退出之前能完成清理任务。

    如果窗口关闭的顺序无关紧要,则可以遍历 NativeApplication.nativeApplication.openedWindows 数组并依次关闭每个窗口。如果顺序的确 很重要,则需提供一种以正确顺序关闭窗口的方法。

    private function onExiting(exitingEvent:Event):void { 
        var winClosingEvent:Event; 
        for each (var win:NativeWindow in NativeApplication.nativeApplication.openedWindows) { 
            winClosingEvent = new Event(Event.CLOSING,false,true); 
            win.dispatchEvent(winClosingEvent); 
            if (!winClosingEvent.isDefaultPrevented()) { 
                win.close(); 
            } else { 
                exitingEvent.preventDefault(); 
            } 
        } 
         
        if (!exitingEvent.isDefaultPrevented()) { 
            //perform cleanup 
        } 
    } 
  3. Windows 应始终通过侦听自己的 closing 事件来处理自己的清理任务。

  4. 在应用程序中仅使用一个 exiting 侦听器,因为调用时间较早的处理函数无法知道随后的处理函数是否将取消 exiting 事件(依赖于执行顺序将是不明智的做法)。