AIR 應用程式叫用與終止

Adobe AIR 1.0 以及更新的版本

本節討論叫用已安裝 Adobe® AIR® 應用程式的方式,以及關閉執行中應用程式的選項與考量。

備註: 只有在 AIR 應用程式安全執行程序中執行的 SWF 內容,才能使用 NativeApplication、InvokeEvent 和 BrowserInvokeEvent 物件。 在 Flash Player 執行階段中、瀏覽器或獨立播放程式 (放映檔) 內、或在應用程式安全執行程序外的 AIR 應用程式中執行的 SWF 內容,無法存取這些類別。

如需叫用以及終止 AIR 應用程式的快速說明與程式碼範例,請到 Adobe Developer Connection 參閱下列快速入門文章:

叫用應用程式

當使用者 (或作業系統) 執行下列操作時,便會叫用 AIR 應用程式:

  • 從桌面殼層啟動應用程式。

  • 從命令列殼層輸入命令來使用應用程式。

  • 開啟某一類型的檔案,而應用程式正好是該類型的預設應用程式。

  • (Mac OS X) 按一下停駐工作列上的應用程式圖示 (不論應用程式目前是否正在執行)。

  • 選擇從安裝程式啟動應用程式 (在全新安裝過程的結束步驟時,或是按兩下已安裝的應用程式 AIR 檔之後)。

  • 當已安裝的版本通知要自行處理應用程式更新時 (由於應用程式描述器檔案中包含 <customUpdateUI>true</customUpdateUI> 宣告),開始更新 AIR 應用程式。

  • 造訪裝載 Flash 標誌或應用程式的網頁,以致指定 AIR 應用程式的識別資訊來呼叫 com.adobe.air.AIR launchApplication() 方法 (應用程式描述器檔案中也必須包含 <allowBrowserInvocation>true</allowBrowserInvocation> 宣告,瀏覽器叫用才會成功)。

每當叫用 AIR 應用程式時,AIR 就會透過 Singleton 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 物件。

應用程式可使用 NativeApplication 物件來註冊偵聽程式,以便處理 invoke 事件:

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 程式的引數均會視為以空白字元分隔的字串,除非引數包括在雙引號內:

引數

陣列

tick tock

{tick,tock}

tick "tick tock"

{tick,tick tock}

"tick" "tock"

{tick,tock}

\"tick\" \"tock\"

{"tick","tock"}

InvokeEvent 物件的 currentDirectory 屬性包含 File 物件,代表從中啟動應用程式的目錄位置。

若是開啟應用程式已註冊的檔案類型導致叫用應用程式,命令列引數即包括該檔案的原生路徑字串 (您的應用程式會負責開啟檔案,或對檔案執行預期的作業)。同樣地,如果應用程式設計成會更新其本身 (而非依賴標準 AIR 更新使用者介面),則當使用者按兩下具有相符應用程式 ID 的應用程式所在的 AIR 檔時,就會包括該 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 應用程式

AIR 應用程式可藉由將 NativeApplication 的 startAtLogin 屬性設定為 true, 設定成目前的使用者登入時自動啟動。一旦完成設定,每當使用者登入便會自動啟動應用程式。應用程式將持續在登入時啟動,直到此設定變更為 false、使用者透過作業系統手動變更設定,或是解除安裝應用程式為止。登入時啟動是執行階段的設定。此設定僅會套用至目前的使用者。應用程式必須已安裝完成,才能成功地將 startAtLogin 屬性設定為 true。如果尚未安裝應用程式 (例如,應用程式是由 ADL 啟動) 卻設定了該屬性,便會擲回錯誤。

備註: 應用程式不會在電腦系統開機時啟動,而是會隨著使用者登入而啟動。

若要判斷應用程式是自動啟動還是由使用者的動作而啟動,可檢查 InvokeEvent 物件的 reason 屬性。若屬性等於 InvokeEventReason.LOGIN,表示應用程式為自動啟動。若採用其他叫用路徑,reason 屬性會等於 InvokeEventReason.STANDARD。若要存取 reason 屬性,則應用程式的目標必須是 AIR 1.5.1 (請於應用程式描述器檔案中設定正確的命名空間值)。

下列簡化的應用程式使用 InvokeEvent 的 reason 屬性,決定發生叫用事件時的反應。若 reason 屬性為「login」,則應用程式會保持在背景。否則,主應用程式會是可見狀態。使用此模式的應用程式一般會於登入時啟動,以完成背景處理或事件監控,並針對使用者觸發的叫用事件開啟視窗。

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 屬性。您也可以檢查 BrowserInvokeEvent 物件的 isHTTPS 屬性,要求叫用應用程式的網站必須使用 HTTPS。

應用程式應該驗證所有傳入的資料。例如,應用程式若預期將傳入特定網站的 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. 各視窗應該負責偵聽自己的 closing 事件,藉以處理各自的清理工作。

  4. 每個應用程式中僅能使用一個 exiting 偵聽程式,因為稍早呼叫的處理常式無從得知後續的處理常式是否會取消 exiting 事件 (依照執行順序進行並不可靠)。