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) APNs(Apple Push Notification service)로부터 알림을 수신합니다.

  • URL을 통해 응용 프로그램을 호출합니다.

  • AIR 응용 프로그램에 대한 식별 정보를 지정하는 com.adobe.air.AIR launchApplication() 메서드를 호출하는 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; 
} 

명령줄 인수 캡처

NativeApplication 객체에 의해 전달된 InvokeEvent 객체에서 AIR 응용 프로그램 호출과 관련된 명령줄 인수가 전달됩니다. 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 응용 프로그램 호출

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 - 내용이 local-with-filesystem 보안 샌드박스에 있습니다.

  • Security.LOCAL_WITH_FILE - 내용이 local-with-filesystem 보안 샌드박스에 있습니다.

  • Security.LOCAL_WITH_NETWORK - 내용이 local-with-networking 보안 샌드박스에 있습니다.

  • 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.autoExit true 인 경우 마지막 응용 프로그램 윈도우를 닫습니다.

  • 사용자가 기본 메뉴에서 응용 프로그램 종료 명령을 선택하는 경우와 같이 운영 체제에서 응용 프로그램 종료 명령을 선택합니다. 이는 Mac OS에서만 가능합니다. Windows 및 Linux에서는 시스템 크롬을 통해 응용 프로그램 종료 명령을 제공하지 않습니다.

  • 컴퓨터를 종료합니다.

종료 명령이 이러한 경로 중 하나로 운영 체제를 통해 조정되는 경우 NativeApplication은 exiting 이벤트를 전달합니다. exiting 이벤트를 취소할 리스너가 없는 경우 열려 있는 모든 윈도우가 닫힙니다. 각 윈도우는 closing 이벤트를 전달한 다음 close 이벤트를 전달합니다. closing 이벤트를 취소할 윈도우가 없는 경우 종료 프로세스가 중단됩니다.

윈도우 종료 순서가 응용 프로그램에서 중요한 문제가 되는 경우 NativeApplication에서 exiting 이벤트를 수신하고 자체적으로 적합한 순서로 윈도우를 닫습니다. 예를 들어 도구 팔레트가 포함된 문서 윈도우를 사용하는 경우 이 방법이 필요할 수 있습니다. 시스템에서 팔레트를 닫았는데 사용자가 일부 데이터를 저장하기 위해 종료 명령을 취소하려는 경우 이러한 작업은 불편하거나 문제를 발생시킬 수 있습니다. Windows에서 exiting 이벤트를 받을 유일한 시간은 마지막 윈도우를 닫은 후입니다(NativeApplication 객체의 autoExit 속성이 true 로 설정된 경우).

모든 플랫폼에서의 일관된 비헤이비어를 제공하려면 종료 시퀀스를 운영 체제 크롬, 메뉴 명령 또는 응용 프로그램 논리를 통해 시작했는지에 상관없이 응용 프로그램을 종료하기 위한 다음과 같은 권장 방식을 관찰합니다.

  1. 항상 응용 프로그램 코드에서 exit() 를 호출하기 전에 NativeApplication 객체를 통해 exiting 이벤트를 전달하고 응용 프로그램의 다른 구성 요소에서 이벤트를 취소하지 않는지 확인합니다.

    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 리스너만 사용합니다.