Flash에서는 Remoting 호출을 위한 클래스가 존재하지 않는다. NetConnection을 가지고 할 수 있으나 Remoting 호출을 위한 전용 클래스가 아니기 때문에 실용적이지 못하다.

Flex에서는 Remoting 호출을 위해서 RemoteObject 클래스가 있다. 하지만 Flex 전용이라 Flash CS3, 4 개발자는 쓸 수 없다.

여기서는 NetConnection을 한번 wrapping하여 Remoting 호출을 구현한 Service 클래스를 소개한다. 이 Service 클래스는 byteArray.org 블로그 운영자가 만든 것으로 필요할 때 사용하면 매우 유용할 것이라 생각한다.

mport org.bytearray.remoting.Service;
import org.bytearray.remoting.PendingCall;
import org.bytearray.remoting.events.FaultEvent;
import org.bytearray.remoting.events.ResultEvent;
 
var service:Service=new Service("HelloRemoting",
"http://localhost:8001/amfphp/gateway.php");
 
var pendingCall:PendingCall=service.sayHello("remoting");
pendingCall.addEventListener(ResultEvent.RESULT,onResult);
pendingCall.addEventListener(FaultEvent.FAULT,onFault);
 
function onResult(e:ResultEvent):void{
trace(e.result);
}
 
function onFault(e:FaultEvent):void{
trace(e.fault);
}

사용법은 위처럼 너무 간단하다. AMFPHP, OpenAMF등으로 Remoting 서비스를 위한 서버를 구축하고 클라이언트는 Flash를 통해 개발하려는 사람에게 매우 유용할 듯 싶다. 또는 Flex가 아닌 순수 ActionScript 3.0 으로만 개발하려는 사람에게도 괜찮을 것 같다.



더 자세한 내용은 아래에 이 코드를 만든 원저자의 글을 보길 바란다.


Making Flash Remoting easier in Flash CS3


ByteArray.org에 기존 Flash Player 9에서 사용되는 corelib의 JPGEncoder보다 더 빠른 Flash Player 10용으로 제작된 JPGEncoder를 만들어 공개했다. 더 빠를 수 있었던 것은 VectorBitmapData.setVector() 때문이다. 기존 Array 형태보다 글쓴이 컴퓨터에서는 2.5배 빠르다고 한다. 아래 프로그램에서 테스트 할 수 있다. 2880 x 2800 비트맵데이타를 encoding 하도록 만들어졌다.

 

 

 

필자는 아직 여러가지 문제로 Flash Player 10용으로 개발하지 못하고 있어 아쉽지만 위와 같은 기능을 앞으로 잘 사용하면 속도개선에 큰 도움이 될 것이라 판단이 된다.

 

저번주 2009년 5월 23일 서울 서초동 비트컴퓨터 멀티미디어관 지하 2층에서 OKGosu.net 세미나가 있었습니다. 저는 "스타플(starpl.com) 서비스에 이용된 Flex 기술" 이라는 제목으로 발표를 했습니다.

 

스타플 홍보도 하고 싶고 함께 기술도 공유하고 싶어서 그랬는지 주제 자체가 너무 커져서 많은 것을 전달하지 못해 약간 아쉬움이 남는 시간이였던 것 같습니다.

 

이번 세미나로 제가 말씀드리고 싶었던 것은 아래 내용이였습니다.

 

1. 스타플 소개

2. Flex를 도입할 때 고려해야할 사항

3. 개발 방법 소개

4. 모듈 개발시 최적화 방법

5. Flex 학습방법

 

한가지만 해도 30분 이상씩 잡아야 제대로 설명이 가능한 건데 너무 많은 주제를 놓고 이 모든 것은 40분 내에 말할려고 했네요. ^^;;

 

약간 보충해서 설명드릴께요.

 

1. 스타플 소개

스타플 구경을 못드렸네요. http://starpl.com 에 가시면 구경하실 수 있답니다.

홍보 동영상을 다시한번 보고 싶으시다면 "홍보영상보기"를 눌러주세요.

스타플에 대해서 더 알고 싶다면 제 블로그에서 스타플로 검색해보세요.

 

2. Flex를 도입할 때 고려해야할 사항

발표시 말씀드렸던 것은 Flex/Flash 중 도입 선택에 대한 문제였습니다. 어떤 경우에는 Flex를 도입하는 것이 맞는 반면 경우에 따라서는 Flex를 쓰는 것이 오히려 문제가 될 수 있습니다. 가령, Flex Framework에서 제공하는 컴포넌트를 거의 사용하지 않는데 Flex를 도입하는 것은 무리입니다. 또한 Flex를 도입하면 쉽게 해결될 것을 Flash로 만들면 그것 또한 문제지요. 이 점을 잘 생각하시고 선택하시길 바래요.

 

3. 개발 방법 소개

제 개인적인 개발 방법을 소개했습니다.

분석-설계-제작-테스트-배포 형태로 된다는 것을 설명드렸죠.

말씀 드리고 싶었던 것은 "기획자, 디자이너간에 커뮤니케이션 중요하다. 서로 존중하면서 대화에 임하라."라는 것과 제작환경 구성시 "ANT, SVN은 꼭 잘 활용해라"라는 것이였습니다. 더불어 통신관련 도식화와 애플리케이션 도식화, 그리고 UML 제작을 개발하기 전에 꼭 해보라는 것이였습니다. 이러한 작업은 앞으로 문서화에도 도움이 되며 다른 사람에게 인수인계할때도 좋고요. 나중에 그 프로그램을 다시 보더라도 내가 짠 것 내가 이해 못하는 꼴이 발생하지 않습니다. ^^

 

4. 모듈 개발시 최적화 방법

SWF가 다른 SWF를 로드하고 Class를 서로 참조하는 일이 발생하는 개발을 하게 되면 모듈 프로그래밍을 한다고 할 수 있습니다. 중요한 것은 다른 SWF간에 정의된 Class중복을 어떻게 해결할 것인가였습니다. 중복 자체가 크게 문제를 일으키는 것은 아니지만 필요없는 Class를 가지고 있기 때문에 프로그램 용량이 그만큼 커집니다. 그래서 중복된 Class를 없애기 위해 RSL도 이용해보고 mxmlc 컴파일러 옵션에 -include-libraries, -load-externs, -link-report 등의 옵션을 사용하는 예도 보여드렸습니다.

 

Main애플리케이션과 모듈이 서로 같은 Class를 담고 있는 경우 그대로 로드하면 어느 한쪽의 클래스를 사용하게 되겠지요. 이때 중요한 개념이 ApplicationDomain인데 제가 이 설명을 빠뜨렸습니다.

 

이 이야기는 다음 링크를 참고하세요.

Flex 모듈 프로그래밍의 기초 - Application domain의 이해 1부

Flex 모듈 프로그래밍의 기초 - Application domain의 이해 2부

 

 

5. Flex 학습법

이 부분은 시간관계상 설명 못드렸네요.

어떻게 보면 매우 중요한 부분인데 ㅎㅎ

제가 말씀 드리고 싶었던 것은 Flex 낚는 어부가 되시라는 거였습니다. Flex라는 물고기만 계속 받아먹기만 하면 발전이 없잖아요. 그래서 Flex 낚는 어부가 되기 위해 이것만은 했으면 좋겠다는 내용이였죠. 설명 못드려 아쉽네요. 시간조정을 못한 제 불찰입니다. ㅜㅜ

 

 

아무튼 짧았지만 좋은 시간이였던 것 같습니다. 제가 발표했던 자료는 아래 링크를 통해 받아가세요.

 

 

다른 분의 자료는 [여기]에서 받아가세요.

 

이번 세미나를 개최하시느라 고생하신 옥상훈(http://okgosu.tistory.com)님 수고 많으셨고요.

주최에 도움을 줬던 데브멘토(http://devmento.co.kr) 관계자 분들도 수고 많으셨습니다.

 

발표자였던 오창훈(http://lovedev.tistory.com/), 이정웅(http://bluemetal.tistory.com/)님도 수고했어요.

 

저는 항상 열려있습니다.

저처럼 바보같은 질문도 괜찮습니다. 반대로 제가 답변 못하더라도 전 부끄럽지 않습니다.

오히려 몰랐던 것을 알게 되니 좋은 것이지요. ^^

대한민국 개발자들이 서로 소통하는데 힘써주셨으면 합니다.

 

 

 

 

Flex, Flash, ActionScript, AIR....

Adobe RIA 기술을 처음 접해본 이들에게 이 용어들의 구분은 매우 생소하다. 앞에 3개는 모두 Flash Player에서 돌아가는 SWF파일을 만들어주는 툴 및 언어이다. AIR는 이러한 것을 확장해서 데스크탑 영역까지 애플리케이션을 제작할 수 있는 기술이라고 할 수 있다.

 

많이 헷갈리는 것중에 하나가 바로 Flex, Flash, ActionScript 의 구분이다. ActionScript는 Flex, Flash 애플리케이션을 개발하는 가장 핵심이면서 기반이 되는 언어이다. Flex는 ActionScript 3.0을 확장해 RIA를 지원하기 위한 일종의 프레임워크이다. Flash는 ActionScript 초기 시절부터 있는 일종의 툴로서 이펙트 및 다양한 효과를 제작하는데 적절하도록 구성된다. 결국 Flex, Flash의 태생은 ActionScript 이고 어떤 목적을 가지고 제작하느냐에 따라 Flex, Flash로 구분해서 개발한다. 물론 어떤 것을 선택하든지 결과물은 SWF이다.

 

재미있는 사실은 Flash는 뭔가 디자인 중심적으로 느껴지는 반면, Flex는 개발적이고 기술적으로 여겨는 풍토가 있다. Flash 개발자라고 말하는 것보다 Flex 개발자라고 하면 연봉협상에도 유리할지 모른다. 하지만 Flash 개발이든 Flex 개발이든 어떤 애플리케이션을 만드냐에 따라 선택이 달라지는 것이지 개발의 질이 달라지는 것은 절대 아니다. 여기서 선택이라는 것은 가령 이렇다. 기업솔루션을 구축해야하는 상황이면 서버 구성 및 데이타 통신, 데이터 관리, 각종 컴포넌트를 아낌없이 지원해주는 Flex로 개발하는 것이 좋다. 하지만 Google Map과 같이 Flex 컴포넌트를 전혀 사용하지 않는다면 Flash 기반이나 순수 ActionScript 기반으로 개발하는 것이 맞다.

 

Adobe에서 Flex Builder 3의 다음 버전을 Flex가 아닌 Flash Builder 4로 지정하기로 했다. Flash Builder 4는 현재 Flash CS4와 같은 툴이 아니라 지금의 Flex Builder와 같이 Eclipse기반으로 만들어진 툴이다. 또한 Flex 4 SDK(현 Flex Gumbo)를 기반으로 Flex 개발환경을 지원한다. 결국 그런거다. Adobe RIA기술의 핵심은 Flash다. 그리고 Flex도 결국 Flash다. 다만 Flex SDK를 기반으로 제작하면 Flex SDK로 만들어진 Flash 애플리케이션인 것이다.

 

차기 버전이 Flex Builder 4 대신 Flash Builder 4로 지정한 것은 여러모로 바람직해 보인다. 일단 같은 SWF 결과물을 만들어 준다는 암시를 지니기 때문에 의미전달이 쉬워진다. 겉모습은 전부 Flash이지만 개발방법은 선택하기 나름인 것이다. Flex SDK를 사용하면 그 사람은 여전히 Flex 개발자인 것이다. 앞으로 디자이너와 개발자간에 협력 도구인 Flash Catalyst는 Flash Builder와 매우 자연스럽게 연동이 된다. 그런데 어떤것은 Flex, 어떤 것은 Flash, 이렇게 되면 뭔가 섞일 수 없는 느낌이 든다.

 

2009년 6월 쯤에 1차 베타버전이 나오고 4분기면 정식 버전이 출시될 것이다. 아무튼 기존 보다 개발의 편의성을 극대화 시킬 수 있는 Flash Builder 4가 나오길 기대한다.

 

이것만은 기억하자.

이제 더 이상 Flex는 Flex Builder를 지칭하지 않는다. Flex는 Flex SDK를 지칭하며 그것을 이용해서 개발하는 툴은 Flash Builder 4인 것이다.

Flash Builder 4는 다음 링크를 통해 다운받으세요.
http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026 

Flex Builder에서 라이브러리 프로젝트를 구성하다가 컴파일시 "unable to export SWC oem" 에러가 발생하며 SWC를 생성하지 못하는 경우가 종종 발생한다. 대부분의 경우, 라이브러리 빌드 Assets 경로에 등록한 파일의 경로가 강제로 수정되거나 삭제되었을 때 발생한다. 이 에러를 해결하기 위해 다음과 같이 진행해보자.

 

이 Assets 경로는 SWC로 만들때 필요한 자원을 포함하기 위한 것으로 comps 컴파일러의 옵션중에 -include-file 을 주는 것과 동일하다.

 

만약 존재하는 asset이 Assets 리스트에 존재하는데도 불구하고 에러가 발생했다면

 

1. Project 메뉴 > Properties > Flex Library Build Path > Assets 탭

2. asset을 uncheck한다.

3. OK버튼을 누른다.

4. 다시 똑같이 돌아가서 해당 asset을 re-check한뒤 OK버튼을 누른다.

 

 

만약 존재하지 않는 asset이 Asset 리스트에 등록되어 있어 에러가 발생했다면 임시로 해당 asset을 추가하는 방법으로 해결한다.

 

1. New > File 에서  하나의 파일을 만든다.

2. Project 메뉴 > Properties > Flex Library Build Path > Assets 탭

3. 새로 만든 파일을 check한다.

4. OK 버튼을 누른다.

5. 다시 돌아가 새로운 파일을 uncheck한 뒤 그것을 삭제한다.

 

 

같은 에러가 발생한 분들에게 도움이 되었으면 한다. ^^

대부분의 Adobe RIA관련 개발자들은 AIR, Flex, Flash 애플리케이션을 개발하기 위해 Flash IDE또는 Flex Builder를 많이 사용할 것 입니다. 또는 FDT(Flash Development Tools)라는 것도 쓰고요. 그런데 이들 툴의 단점은 유료라는 겁니다.

 

이런 툴이 없어도 Flex, AIR의 경우 개발이 가능하다만 개발툴없이 개발하는 것은 그만큼 생산력이 떨어지지요.

 

지금 소개하는 FlashDevelop은 Flash 기반 개발툴입니다. 중요한 것은 무료인데다가 개발도 큰 불편함이 없다는 장점이 있습니다. 또한 수시로 업데이트되어 관리가 되는 툴입니다. FlashDevelop은 일단 Flex Builder나 FDT에 비해 가볍고, AS2, AS3 환경에서도 모두 개발이 가능하다는 장점이 있습니다. 몇가지 기능적 제한(?)이 있을지 모르겠지만 개발하는데 크게 무리가 없다는 생각이 듭니다. 제게 필요한 ANT 개발 및 SVN 소스 관리가 되는지는 잘 모르겠군요.

 

우리나라에서도 FlashDevelop을 이용해 개발하시는 분들이 꽤 있나봅니다. Naver 카페에 "FlashDevelop을 쓰는 사람들"이 만들어졌더군요. 올해 4월 30일에 만들어졌으니 1개월도 안된 신생 카페입니다. 그래도 벌써 회원수가 100명이 넘었습니다. FlashDevelop에 대한 정보를 공유하고자 하는 사람에게 매우 유용할 것 같습니다. 필자도 FlashDevelop를 많이 써본적 없고 관련 정보에 매말라 있었는데 이렇게 좋은 카페가 생겨서 흐뭇하네요. 기회가 닿으면 FlashDevelop으로도 개발해보고 싶네요.

 

좋은 카페를 만들어 주신 재규어님께 감사합니다. ^^

 

 

 

Essensial ActionScript 3.0(이하 EAS)책에 보면 Stage의 invalidate() 메소드와 Event.RENDER의 활용법이 나와있다.(한국어 판이 나올 예정이니 기대가 크다. ^^)

 

invalidate() 메소드는 Event.RENDER 이벤트를 발생해주는 역할을 한다. 이것을 적절하게 사용하면 화면갱신을 필요할 때만 적절하게 수행할 수 있기 때문에 애플리케이션의 전체적인 퍼포먼스를 향상할 수 있게 된다. Event.RENDER 이벤트는 invalidate() 메소드의 호출 외에도 MouseEvent, KeyboardEvent등의 updateAfterEvent() 메소드를 호출해도 발생한다.

 

EAS 23장에 Event.RENDER를 이용한 최적화에서 최종 예를 보면 다음과 같다.(올려도 되는건가??)

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);

   if (hasChanged( )) {
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

왜 저렇게 만들었는가 이유를 알고 싶다면 직접 분석하거나 EAS를 구입해보는 것이 좋겠다. 이것만은 기억하자 저렇게 만든 이유는 witdh, height가 바뀔때마다 draw() 메소드가 호출되어 쓸데없이 렌더링되는 것을 방지하기 위함이다.

 

본인의 생각에 위 코드의 최적화 부분에서 약간 문제점이 있다는 생각이 든다. 그것은 Ellipse객체가 Stage에 추가될 때 호출되는 addedToStageListener()내에 정의된 stage.addEventListener(Event.RENDER, renderListener)이 있는 부분이다.

 

이는 만약 100개의 Ellipse객체가 Stage에 추가된 경우 100개의 renderListener()이 등록된 것과 동일하다. Event.RENDER 이벤트가 발생할 때 renderListener()에서 hasChanged()메소드를 이용해 변경해야하는 경우에만 draw()를 호출한다고 하지만 그래도 여전히 100개의 renderListener() 이벤트 청취자 함수가 호출되는 것은 마찬가지이다.

 

렌더링하는 시점에만 stage.addEventListener(Event.RENDER, renderListener)로 renderListener() 메소드를 등록하고 renderListener()이 호출되면 stage.removeEventListener(Event.RENDER, renderListener)를 실행하면 쓸데없이 renderListener() 메소드가 호출되는 것을 막을 수 있지 않을까? 가령, 1개의 Ellipse객체만 다시 그려야하는 경우 그 객체의 renderListener() 메소드만 호출하면 되기 때문에 전체 퍼포먼스를 향상하는데 더 좋을 것이다. 그래서 본인은 위 Ellipse 클래스를 아래처럼 수정해봤다.

 

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
    stage.removeEventListener(Event.RENDER, renderListener);
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   if (hasChanged( )) {
		stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   clearChanged();
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    clearChanged();
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

자, 위처럼 하면 필요할때만 renderListener()를 호출하므로 더 최적화 되었다고 할 수 있겠다.

 

이 방식을 응용하여 Sprite를 확장해서 필요할 때만 렌더링해주는 Flex의 UIComponent와 같은 추상클래스(?)를 만들어주면 어떨까?

 

이름은 flash.display.UISprite로 하고 그에 대한 인터페이스는 flash.display.IUISprite로 정했다. UISprite에는 Flex의 UIComponent에서 제공하는 invalidateProperties(), invalidateDisplayObject()를 Event.RENDER를 이용해 흉내낸 클래스이다. Flex SDK를 활용하지 않고 개발할 때 이 클래스는 최적화된 Rendering을 하는데 도움을 줄 수 있을 것이다.

 

 

package flash.display
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * ActionScript 3.0 기반으로 만드는 프로젝트의 시각화 객체를 사용해야할 경우 이것을 확장해서 사용하도록 한다.
* 사용법은 Flex의 UIComponent와 흡사하다. * * @author Yongho, Ji (jidolstar@gmail.com) * @since 2009.05.12 */ public class UISprite extends Sprite implements IUISprite { private var isInvalidateProperties:Boolean = true; private var isInvalidateDisplayObject:Boolean = true; private var isCreatedChildren:Boolean = false; private var isCreationComplete:Boolean = false; public function UISprite() { super(); this.addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true ); this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true ); } /** * 고정적인 시각화 객체를 추가할 때 사용하는 함수로 Override 해서 사용한다. */ protected function createChildren():void { } /** * 속성이 변경되었을 때 처리하는 함수로 Override 해서 사용한다. */ protected function commitProperties():void { } /** * 시각화 객체가 새로 렌더링할때 사용하는 함수로 Overide해서 사용한다. */ protected function updateDisplayObject():void { } /** * 라이프 사이클이 모두 완료되면 이 함수가 호출된다. * 단 한번만 호출되는데 필요할 때 Override해서 사용하면 되겠다. */ protected function creationComplete():void { } /** * @inheritDoc */ public function validateProperties():void { isInvalidateProperties = true; commitProperties(); } /** * @inheritDoc */ public function validateDisplyObject():void { isInvalidateDisplayObject = true; updateDisplayObject(); } /** * @inheritDoc */ public function invalidateProperties():void { if( isInvalidateProperties ) return; isInvalidateProperties = true; invalidate(); } /** * @inheritDoc */ public function invalidateDisplayObject():void { if( isInvalidateDisplayObject ) return; isInvalidateDisplayObject = true; invalidate(); } /** * 무효화 */ protected function invalidate():void { if( stage != null ) { stage.addEventListener( Event.RENDER, onRender, false, 0, true ); stage.invalidate(); } } /** * Stage에 추가 되었을때 처리 */ private function onAddedToStage( event:Event ):void { //자식 추가, 처음에만 한번 호출됨 if( !isCreatedChildren ) { isCreatedChildren = true; createChildren(); } //무효화 처리 if( isInvalidateProperties || isInvalidateDisplayObject ) { invalidate(); } } /** * Stage에서 제거 되었을 때 처리 */ private function onRemovedFromStage( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } } /** * Render 이벤트 처리 */ private function onRender( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } //commit properties if( isInvalidateProperties ) { isInvalidateProperties = false; commitProperties(); } //update display object if( isInvalidateDisplayObject ) { isInvalidateDisplayObject = false; updateDisplayObject(); } //creation complete. call only once if( !isCreationComplete ) { isCreationComplete = true; creationComplete(); } } /** * @inheritDoc */ public function get childrenCreated():Boolean { return isCreatedChildren; } /** * @inheritDoc */ public function get creationCompleted():Boolean { return isCreationComplete; } /** * @private */ override public function set width(value:Number):void { super.width = value; invalidateDisplayObject(); } /** * @private */ override public function set height(value:Number):void { super.height = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleX(value:Number):void { super.scaleX = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleY(value:Number):void { super.scaleY = value; invalidateDisplayObject(); } /** * override해서 trace문을 대신 정의해서 사용한다. * * @example * * override protected function TRACE( ...args ):void * { * trace( prototype.constructor.toString(), args ); * } * */ protected function TRACE( ...args ):void { //trace( prototype.constructor.toString(), args ); } } }

 

UISprite는 검증되지 않았고 프로젝트에 적용하기 위해 테스트 단계에 있다.

 

전체 소스는 아래서 다운로드 받는다.

 

 

이 클래스에 대한 문제점 및 다른 의견이 있다면 언제든지 댓글 및 피드백을 걸어주었으면 한다.

 

오늘 ACC 오픈 세미나가 열립니다. ACC는 Adobe Community Champion의 약어로 Adobe Flash Platform 관련 전문인들로 구성된 그룹(현 23명)을 말하는 것입니다. 이들이 주최가 되는 오픈 세미나입니다.

 

오늘( 5월 16일 토요일 ) 오후 1:00~6:00 까지 강남교보타워 23층에서 열립니다.

 

이번에 특별한 활동은 ACC릴레이발표와 OST 주제별 토론입니다.

 

이번 ACC 릴레이 발표는 효율적인 통신을 위한 LCDS와 HTML로 만드는 Adobe AIR 애플리케이션이라는 주제로 발표될 것입니다.

 

OST(Open Source Technology)는 이번 세미나에 참석한 모든 분들이 함께 자유로운 주제를 가지고 토론하거나 기술을 공유하는 자리입니다. 이전 세미나가 1:n 으로 진행되었다면 이번 세미나는 n:n이 될 수 있겠네요. 참 좋은 시도일 것 같습니다.

 

저도 ACC에 속해서 오늘 가게 됩니다.

오시는 분들과 좋은 시간을 가졌으면 좋겠네요. ^^

 

ACC 오픈 세미나 공지

ACC 소개

 

Flex Builder 3에서 ANT로 원하는 파일을 FTP로 전송하는 방법에 대해서 소개한다.

 

참고로 Flex Builder에서 ANT 환경을 구성하는 방법은 필자가 이전에 작성한 “Flex Builder 3에서 ANT 사용하기”를 읽어보면 되겠다.

 

ANT 환경에서 FTP를 이용하는 경우는 필자의 경우 asdoc등으로 만들어진 문서를 웹에 배포할 때였다. FTP는 ANT에서 기본적으로 제공하는 명령이 아니기 때문에 추가적으로 라이브러리를 설치해야 한다. 이 라이브러리는 Apache Jakarta프로젝트에서 개발한 Commons Net를 사용하면 된다.

 

FTP작업을 위한 환경 만들기

다음과 같은 순서로 환경을 만든다.

  1. Commons Net 2.0 Binary버전을 다운받아 압축을 푼다. 그리고 해당 폴더를 관리하기 편한 위치에 옮겨둔다.
  2. Flex Builder의 메뉴에서 Windows > Perferences를 선택하면 아래와 같은 창이 나온다. 왼쪽 메뉴에 Ant > Runtime을 선택한다. 그리고 Classpath 탭을 누르고 Ant Home Entries를 선택한다. 오른쪽에 Add External JARs… 버튼을 눌러 Commons Net안에 commons-net-2.0.jar와 commons-net-ftp-2.0.jar을 추가하도록 한다.

 

 

FTP 전송을 위한 ANT Script 작성

ANT 스크립트를 아래와 같이 작성한다.

 

<ftp server="${ftp.server}"
port="${ftp.port}"
remotedir="${ftp.dir}"
userid="${ftp.userid}"
password="${ftp.password}"
depends="yes"
binary="no"
>
<fileset dir="{dir.docs}">
<include name="**/*.html"/>
</fileset>
</ftp>

 

위 스크립트를 실행하면 아래처럼 비슷하게 Flex Builder 3의 Console창에 출력하며 전송이 될 것이다.

Buildfile: D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\build\as3utils.build.xml
asdoc.ftp:
[ftp] sending files
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-classes.html
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-index-A.html
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-index-B.html
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\all-index-C.html

[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\style.css
[ftp] transferring D:\STARPL_DEV_02\Map\mapdev3\20090421\as3utils\docs\title-bar.html
[ftp] 75 files sent
BUILD SUCCESSFUL
Total time: 15 seconds

 

FTP 사용시 각종 옵션은 http://ant.apache.org/manual/OptionalTasks/ftp.html 를 참고한다.

 

 

이 글은 Flex Builder 3환경에서 ANT를 사용하여 개발의 효율성을 높이는 방법을 이해하는데 목적이 있다. 처음 ANT를 접하는 사람을 위해서 ANT가 무엇이고 사용해야하는 필요성도 언급한다.

 

ANT란?

 

ANT(http://ant.apache.org/)는 자바기반의 빌드 자동화 툴이다. 자바개발자라면 한번쯤은 사용해봤을 것이다. ANT는 일반 개발툴의 개발한계를 극복하는데 유용하다. ANT를 이용하면 여러가지로 분산된 일을 단 한번에 처리할 수 있고 또한 처리 순서를 쉽게 배치할 수 있어 매우 유용하다. 가령 어떤 작업을 처리를 위한 프로세스를 아래와 같이 한다고 하자.

 

  • build 폴더 생성
  • build 폴더 안에 classes 폴더 생성
  • src폴더 안에 있는 소스를 컴파일한 뒤 결과를 classes 폴더로 전송
  • 결과물을 테스트 공용 FTP 서버로 전송
  • 문서제작

 

위와 과정을 손수 하나씩 처리하려고 하면 귀찮기 짝이 없을 것이다. ANT는 이러한 처리를 단 한번에 처리할 수 있도록 해서 작업의 능률을 높여준다.

 

ANT는 ANT자체로도 충분히 사용할 수 있으나  Eclipse나 Flex Builder등에 플러그인으로 설치하여 사용할 수도 있다. ANT는 기존 개발툴에서 개발시 극복하지 못하는 상황을 해결해주는 좋은 도구이다.

 

ANT는 XML형태로 작성된 문서를 이용하며 기본적인 문법만 익히면 쉽게 활용이 가능하다. 여기서는 ANT에 대한 전반적인 지식을 다루기에는 무리가 있으므로 해당 문서를 검색하거나 관련서적을 구입해서 학습하면 좋겠다.

 

 

Flex Builder 3의 개발환경 이해하기

ANT를 사용하기에 앞서 Flex Builder 3의 개발 환경에 대해 어느 정도 이해가 필요하다. Flex Builder 3는 별다른 어려운 설정이 없이 Flex/AIR 프로젝트, 라이브러리 프로젝트, ActionScript 3.0프로젝트 등의 개발 환경을 구성해준다. Eclipse Flex Builder 3 Plugin을 설치해도 이와 동일하게 개발이 가능하다.

 

Flex Builder를 사용하지 않는다면 여러분은 다음과 같은 방법으로 콘솔 환경에서 컴파일을 해야한다. (이는 한가지 예시일 뿐이다.)

 

compc -namespace http://www.adobe.com/2006/foo manifest.xml -source-path .
	-include-namespaces http://www.adobe.com/2006/foo -include-classes mx.containers.MyWindow
	-include-file MyWindow.png mx/containers/MyWindow.png
	-output='MyWindow.swc'

 

아래는 위 컴파일시 사용되는 manifest.xml이다.

<?xml version="1.0"?>
<componentPackage>
	<component id="MyWindow" class="mx.containers.MyWindow"/>
</componentPackage>

 

위에서 compc 명령어는 Flex SDK의 bin폴더에 있는 컴파일러로 AS3 라이브러리의 결과물인 SWC를 만들 때 사용하는 명령어이다. Flex Builder는 개발자가 이런 명령에 대한 자세한 내용을 모르더라도 아주 간단하게 SWC를 만들 수 있도록 한다. Flex Builder를 사용하지 않으면 해당 명령어를 알아야 한다고 하니 Flex Builder가 고마워진다. ^^;

 

위처럼 SWC를 만들때 사용하는 라이브러리 프로젝트 외에 애플리케이션을 만드는 Flex 프로젝트나 ActionScript 3.0 프로젝트의 경우에는 mxmlc 명령어를 사용한다. mxmlc 명령어는 일반 애플리케이션 이외에도 CSS, 리소스 모듈, 모듈 등을 컴파일할 때도 사용한다. 또한 Class별 문서제작은 asdoc 명령어를 사용한다. Flex Builder를 이용하면 compc, mxmlc 등을 몰라도 최소한의 개발이 가능하다는 것을 아는 것이 중요하다.

 

compc, mxmlc, asdoc과 같은 명령어는 Flex Builder가 설치해 있는 MS Window 환경이라면 C:/Program Files/Adobe/Flex Builder 3/sdks/{Flex SDK 버전}/bin 폴더 내에 있다. 여기서 {Flex SDK 버전} 부분은 언제든지 Flex SDK의 다른 버전을 설치해 사용할 수 있다는 것을 의미한다. Flex SDK 다운로드 페이지에서 다운받아 여기에 복사한 뒤 사용하면 되겠다.

 

아래는 Flex Builder 3에 있는 Flex SDK들이다. 개발자는 필요에 따라 여기에 최신 SDK를 복사해서 사용한다.

 

 

아래는 각각 SDK폴더의 bin 폴더 내에 있는 명령어들을 보여준다. 여기에 위에서 언급한 mxmlc, compc등이 있다는 것을 확인하자. Flex Builder는 이들 명령어를 이용해 개발자가 구체적으로 알 필요 없이 내부적으로 SWF, SWC등과 같은 결과물을 만들어 낸다.

 

위 경로는 MS Window XP일 때이다. 다른 운영체제(Mac OS, Linux)의 경우는 다르다.

 

ANT를 언제 사용해야하는가?

Flex Builder 3가 개발의 편의성을 제공함에도 불구하고 Builder에서 제공하는 컴파일 방법만 이용하면 개발 규모 및 방향에 따라 개발의 한계에 도달할 수 있다. 예를 들어, 잦은 라이브러리 변경은 라이브러리가 수정될 때마다 그 라이브러리를 이용하는 프로젝트도 재컴파일하게 되어 결과물을 보여주므로 그만큼 결과를 보는데 오랜 시간이 걸리는 문제가 발생할 수 있다. 또는 Flex Builder 3에서 제공하는 컴파일 방식으로는 능동적으로 다른 프로젝트간에 결과물을 공유할 수 없다. 가령, 다른 프로젝트의 bin폴더의 swc를 참조해서 컴파일해야하는 경우나 만든 프로젝트 결과물을 FTP로 바로 올려야하는 경우, 또는 여러개의 SWF를 복사해서 다른 폴더에서 사용할 수 있도록 배치하는 경우는 Flex Builder 3에서 제공하는 기능만으로는 불가능하다.

 

몇가지 예만 들었지만 Flex Builder만으로 할 수 없는 일들이 종종 생긴다는 것을 이해하는 것이 중요하다. 이러한 이유로 개발 규모 및 방향에 따라 ANT를 도입하여 Flex Builder로만 개발할 때의 단점을 극복하여 능동적이고 가벼운 컴파일링 및 프로젝트간 컨텐츠 공유등을 할 수 있도록 한다.

 

ANT를 도입하게되면 Flex Builder가 하지 못하는 것을 극복하는 것은 사실이지만 개발 자체를 쉽게 해주는 것은 아니다. 왜냐하면 앞서 설명한 mxml, compc와 같은 명령어를 사용하는 방법을 익혀야하며 또한 ANT자체 문법도 익혀야하기 때문이다. 대신 앞서 설명한 한계를 극복하는데 ANT의 선택은 좋은 방법이 될 수 있다.  ANT를 분별없이 무조건 도입하면 오히려 개발 능률을 떨어뜨리게 된다. 그러므로 Flex Builder로 개발 한계에 도달했을 때만 도입하도록 한다.

 

Flex Builder에서 ANT 개발환경 구축하기

Flex Builder 3부터 ANT를 지원하고 있다. 이 말은 Flex Builder 3를 설치하면 ANT관련 Plugin이 설치가 되어 있다는 것을 의미한다. 아래는 Flex Builder 3의 plugins에 ANT가 설치되어 있는 것을 보여주고 있다. 

 

1. JDT(Eclipse Java Development Tools) 설치

 

Flex Builder 3부터는 ANT를 지원한다. 하지만 ANT를 사용하려면 Flex Builder에 JDT를 설치해야한다.  다음 문서에서 설명하는 것처럼 JDT를 설치하도록 한다.

 

Adding the ANT support in Flex Builder 3

 

또는

http://www.flex-tutorial.fr/2009/09/04/installer-ant-dans-flex-builder-3/

 

2. 설치된 ANT 확인

 

JDT를 설치한 후 Flex Builder를 재실행하면 Window->Other View에서 아래와 같이 Ant를 선택할 수 있다.

 

 

선택하면 Flex Builder에 아래와 같은 Ant View 창을 볼 수 있다. 이 창에 필요한 ANT XML문서를 놓아 사용하면 되겠다.

 

 

ANT에 관련된 설정은 Flex Builder 메뉴에서 Window->Preferences를 선택하면 아래와 같은 창이 나오고 좌측 메뉴에서 Ant를 선택하면 각종 설정을 할 수 있다. 대부분의 경우 기본설정으로 두면 되겠다.

 

 

 

Flex SDK 환경에서 ANT 개발 환경 구축하기

참고로 Flex Builder와 상관없이 Flex SDK로만 개발하거나 Flex Builder가 아닌 다른 개발툴을 사용하고자 한다면 아래와 같은 방법으로 환경을 조성하면 된다. (Window XP 기준)

 

1. Ant를 다운로드 받아 설치한다.

 

http://ant.apache.org/bindownload.cgi

 

현재 최신 버전은 1.7.1이다. C에 압축푼다.  그리고 C:/ant1.7.1에 설치한다. 다음으로 환경변수를 등록하는데 시스템 변수로 Path에 C:/ant1.7.1/bin;을 넣어주면 되겠다. 다른 운영체제의 경우는 환경변수 등록하는 방법이 다를 것이다.

 

 

환경변수 추가 후 다음과 같이 콘솔창 띄우고 ant 명령을 줘본다. build.xml이 없으므로 오류메시지 발생하며 정상적으로 실행된 것이다.

 

 

2. Flex SDK의 ant/lib 폴더에  flexTasks.jar를 Ant설치 폴더(C:/ant1.7.1)의 lib폴더에 복사한다.

 

3. build.xml를 구성하여 Flex,ActionScript 3.0로 개발된 것을 콘솔창에서 ant 명령을 이용해 컴파일 및 실행등을 하면 되겠다.

 

이에 대한 자세한 내용은 ANT관련 서적 및 Using Flex Ant Tasks(Flex 3) 문서를 참고한다.

 

Flex Builder 3 환경에서 ANT로 개발해보기

ANT를 이용해 mxmlc, compc, html-wrapper등을 이용하는 방법은 Flex Live docs의 Using Flex Ant Tasks 에 대부분 소개되어 있다. 그리고 Flex 컴파일러인 mxmlc, compc을 사용할 때 옵션은 Using the Flex Compliers문서에 대부분 나와 있다. 이 두 가지만 습득하면 ANT를 이용한 개발 방법은 대략 익히게 된다.

 

영어가 부족하다면 현 ACC(Adobe Community Champion)인 이만영님이 쓰신 Flex Ant Task를 이용한 자동화 빌드 구축하기 문서를 읽어보자. Flex Builder 2기준으로 만든 문서이긴 하지만 지금도 매우 유용하다.

Flex Builder에서 ANT를 이용한 개발은 소개한 문서만 익히면 어느 정도 알 수 있다. 중요한 것은 직접 따라 해보는 것이 중요하다.

 

여기서는 간단하게 Flex Builder 3에서 ANT를 활용하는 방법을 익혀본다.

개발목표는 여러 개의 위젯을 만들고 그 위젯을 로드하는 테스터 프로그램을 만드는 것이다. ActionScript 3.0 프로젝트로 개발할 것이다. 만들어진 결과물은 왼쪽 그림에서 보여주는 것과 같이 bin폴더에 들어가며 위젯의 경우 bin/widgets에 들어간다. 이러한 조건을 만족하도록 개발하려면 Flex Builder 3의 컴파일 기능만 가지고는 안 된다. 왜냐하면 Flex Builder 3에서 기본적으로 제공하는 컴파일 방식으로는 widgets폴더와 같이 다른 폴더에 들어가도록 만들 수 없기 때문이다. 이 조건 하나만으로도 수동작업을 해야 하는 개발의 어려움이 발생한다. 하지만 ANT를 사용하면 이 문제를 깔끔하게 해결할 수 있게 된다.

 

아래 예제 소스 다운로드 :  MyWidgets.zip

 

 

1. ActionScript 3 프로젝트를 만든다.

프로젝트 명은 MyWidgets 라고 하자.

 

2. src 폴더에 IWidget, AbstractWidget을 만들자.

IWidget는 init()함수만 선언한다.

package
{
	/**
	 * 위젯 인터페이스
	 */
	public interface IWidget
	{
		/**
		 * 초기화 함수
		 */
		function init():void;
	}
}

그리고 AbstractWidget는 IWidget을 구현하고 Sprite를 확장해서 만든다.

package
{
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;

	/**
	 * 위젯 추상 클래스
	 */
	public class AbstractWidget extends Sprite implements IWidget
	{
		/**
		 * 추상클래스 생성자
		 */
		public function AbstractWidget()
		{
			super();
			this.addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler );
		}

		/**
		 * @private
		 */
		private function addedToStageHandler( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, addedToStageHandler );
			init();
		}

		/**
		 * @inheritDoc
		 */
		public function init():void
		{
			throw new Error("Override 정의해서 사용가능합니다.");
		}

	}
}

 

3. Widget 3개를 만들자.

이것은 네모, , 세모의 모양을 가지는 위젯이다. 위젯 이름은 각각 Widget1, Widget2, Widget3로 만든다. 이들 위젯은 모두 AbstractWidget을 확장해서 구현한다. 그리고 IWidget init()함수를 override한 뒤 해당 모양을 그려준다.

 

package
{
	import flash.display.Shape;
	import flash.events.Event;

	[SWF(width='100', height='100',backgroundColor='#000000')]
	/**
	 * Widget1
	 */
	public class Widget1 extends AbstractWidget
	{
		/**
		 * 생성자
		 */
		public function Widget1()
		{
			super();
		}

		/**
		 * @inheritDoc
		 */
		override public function init():void
		{
			var shape:Shape = new Shape;
			shape.graphics.clear();
			shape.graphics.lineStyle( 2, 0xff0000, 1 );
			shape.graphics.beginFill( 0xffffff, 1 );
			shape.graphics.drawRect( 0, 0, 100, 100 );
			shape.graphics.endFill();
			addChild( shape );
		}

	}
}

 

4. MyWidgets에 만든 3개의 위젯을 로드하는 프로그램을 만든다.

MyWidgets는 일종의 위젯 테스트용이다. IWidget을 구현한 어떤 위젯이든 올릴 수 있다. 인터페이스 IWidget은 다형성을 고려하기 위한 것이다. 동작방식은 단순하다. 숫자 1부터 3까지 키를 누르면 해당 위젯이 Loader를 통해 로드되어 렌더링된다.

 

package {
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.KeyboardEvent;
	import flash.net.URLRequest;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;

	/**
	 * 위젯 테스터
	 */
	public class MyWidgets extends Sprite
	{
		private var loader:Loader;
		private var loadedWidget:IWidget;

		private var widgetList:Array = [ 'Widget1', 'Widget2', 'Widget3' ];

		/**
		 * 생성자
		 */
		public function MyWidgets()
		{
			super();
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.addEventListener(KeyboardEvent.KEY_DOWN, keyboardEventHandler );
		}

		/**
		 * @private
		 * 키보드로부터 숫자를 받아 해당 위젯을 로드한다.
		 */
		private function keyboardEventHandler( event:KeyboardEvent ):void
		{
			var widgetNumber:int = event.charCode-48;
			if( widgetNumber < 0 || widgetNumber > widgetList.length ) return;

			var widgetURL:String = "widgets/" + widgetList[widgetNumber-1] + ".swf";
			if( loader == null )
			{
				loader = new Loader();
				addChild( loader );
			}

			var request:URLRequest = new URLRequest( widgetURL );
			var context:LoaderContext = new LoaderContext( false, new ApplicationDomain( ApplicationDomain.currentDomain ) );
			loader.load( request, context );
		}
	}
}

 

5. ANT를 구성한다.

프로젝트 폴더에 build폴더를 만들고 거기에 build.xml과 build.properties 파일을 만들고 다음과 같이 스크립트를 코딩한다.

 

아래는 build.properties이다.

# -----------------------------------------------------------------
# User-Defined Paths
#
# Modify these path values to reflect paths on your system
# -----------------------------------------------------------------

# The location of the Flex 2 SDK on your sytem.
flex3sdk.home.dir = C:/Program Files/Adobe/Flex Builder 3/sdks/3.3.0
flex3sdk.bin.dir = C:/Program Files/Adobe/Flex Builder 3/sdks/3.3.0/bin
flex3sdk.lib.dir = C:/Program Files/Adobe/Flex Builder 3/sdks/3.3.0/frameworks/libs

# Note that the locale dir uses the {locale} token at the end to specify the directory
# of language-specific files.  This is replaced by the compiler with the locale defined
# by the locale property below.
flex3sdk.locale = en_US
flex3sdk.locale.dir = C:/Program Files/Adobe/Flex Builder 3/sdks/3.3.0/frameworks/locale/{locale}

asdoc.exe = ${flex3sdk.bin.dir}/asdoc.exe
compc.exe = ${flex3sdk.bin.dir}/compc.exe
mxmlc.exe = ${flex3sdk.bin.dir}/mxmlc.exe

# The debug player is necessary here because it writes trace statements to a flashlog.txt
# file.  This allows us to examine the .txt file and determine the status of unit tests
# in an automated fashion.
flashDebugPlayer.exe = C:/Program Files/Adobe/Flex Builder 3/Player/10/win/FlashPlayer.exe

# -----------------------------------------------------------------
# Project Paths - DO NOT MODIFY
# -----------------------------------------------------------------
build.dir = ${basedir}/build
src.dir = ${basedir}/src
bin.dir = ${basedir}/bin
docs.dir = ${basedir}/docs

 

아래는 build.xml이다.

 

<?xml version="1.0" encoding="utf-8"?>
<project name="MyWidgets" basedir="../">

	<!-- 이 build 스크립트에서 사용할 variable 및 path 정의  -->
	<property file="./build/build.properties" />

	<!-- 해당 properties가 설정 되어 있는가 확인 -->
	<target name="properties">
		<fail unless="asdoc.exe">The "asdoc.exe" property must be set in ${build.dir}/build.properties.</fail>
		<fail unless="compc.exe">The "compc.exe" property must be set in ${build.dir}/build.properties.</fail>
		<fail unless="mxmlc.exe">The "mxmlc.exe" property must be set in ${build.dir}/build.properties.</fail>
	</target>	

	<!-- bin 폴더 생성 -->
	<target name="mkdir bin">
		<mkdir dir="${bin.dir}"/>
		<mkdir dir="${bin.dir}/widgets"/>
	</target>

	<!-- bin 폴더 삭제 -->
    <target name="clean">
        <delete includeEmptyDirs="true">
            <fileset dir="${bin.dir}"/>
        </delete>
    </target>	

	<!-- 모두 컴파일 -->
	<target name="complie all" depends="complie MyWidgets, complie widget1, complie widget2, complie widget3"/> 

	<!-- 위젯 테스터 컴파일 -->
    <target name="complie MyWidgets" depends="properties,mkdir bin">
		<exec executable="${mxmlc.exe}" dir="${basedir}">
			<arg line="'${src.dir}/MyWidgets.as'" />
			<arg line="-o '${bin.dir}/MyWidgets.swf'" />
		</exec>
    </target>	

	<!-- 위젯 테스터 실행 -->
	<target name="run MyWidgets" depends="complie MyWidgets">
		<exec executable="${flashDebugPlayer.exe}" spawn="yes">
			<arg line="${bin.dir}/MyWidgets.swf" />
		</exec>
	</target>

	<!-- 위젯 1 컴파일 	-->
    <target name="complie widget1" depends="properties,mkdir bin">
		<exec executable="${mxmlc.exe}" dir="${basedir}">
			<arg line="'${src.dir}/Widget1.as'" />
			<arg line="-o '${bin.dir}/widgets/Widget1.swf'" />
		</exec>
    </target>

	<!-- 위젯 2 컴파일 	-->
    <target name="complie widget2" depends="properties,mkdir bin">
		<exec executable="${mxmlc.exe}" dir="${basedir}">
			<arg line="'${src.dir}/Widget2.as'" />
			<arg line="-o '${bin.dir}/widgets/Widget2.swf'" />
		</exec>
    </target>

	<!-- 위젯 3 컴파일	-->
    <target name="complie widget3" depends="properties,mkdir bin">
		<exec executable="${mxmlc.exe}" dir="${basedir}">
			<arg line="'${src.dir}/Widget3.as'" />
			<arg line="-o '${bin.dir}/widgets/Widget3.swf'" />
		</exec>
    </target>

</project>

 

만들어진 build.xml은 우측 화면과 같이 Ant View에 드래그해서 붙여 넣는다. 이로써 ANT를 이용해 만들어진 ANT 스크립트를 실행할 수 있게 된다.

 

build.xml은 ANT에서 구동되는 스크립트이다. <project>로 감싸져 있고 <property>와 <target>이 존재한다. <property file="./build/build.properties" />는 build.properties를 로드해서 build.xml에서 사용할 property를 참고하는 역할을 한다. <target>은 하나의 실행단위라고 생각하면 되겠다. <target>은 고유한 name을 가진다. 이 name이 Ant View에 표시되며 표시된 name을 더블클릭하면 <target>에 정의된 코드가 실행되는 것이다.

 

<target> name이 mkdir bin은 bin 폴더와 bin/widgets 폴더를 생성한다. 여기서 ${bin.dir}은 변수로서 build.properties에 정의했다.

 

<target> name이 clean인 것은 만들어진 bin폴더와 widgets폴더를 지운다.

 

<target> name이 complie widget1인 것은 src폴더내에 Widget1.as를 컴파일하여 bin/widgets에 Widget1.swf라는 이름으로 출력하도록 한다. 여기서 Flex SDK에서 제공하는 mxmlc를 사용하게 된다. 어떤 mxmlc를 사용할지는 build.properties에 정의했다.

 

<target> name이 complie MyWidgets는 src폴더내에 MyWidgets.as를 컴파일해서 bin에 MyWidgets.swf라는 이름으로 출력한다.

 

<target> name이 complie all은 <target> name이 complie MyWidgets, complie widget1, complie widget2, complie widget3 로된 <target>을 전부 실행한다. 이처럼 target은 depends 옵션을 이용해 다른 target과 묶어서 실행이 가능하다.

 

<target> name이 run MyWidget은 MyWidget.as를 컴파일한 뒤에 bin/MyWidget.swf를 실행하도록 한다.

 

이것만 보더라도 ANT를 이용해서 프로젝트를 어떻게 관리할 수 있을지 대략적으로 느껴질 것이다. 이처럼 하나의 프로젝트에서 다양한 애플리케이션을 원하는 경로에 컴파일하여 출력할 수 있고 활용해볼 수 있다. ANT에서는 copy, delete등과 같은 명령어를 지원하므로 이들을 적극 활용하면 프로젝트 관리가 더욱간편해질 수 있다. 게다가 결과물을 FTP 전송, SVN 공유, 다른 프로젝트간에 연동도 아주 쉽게 적용할 수 있다. 지금 예제는 debug 용이 아니지만 원한다면 debug도 할 수 있게 꾸밀 수 있다. 또한 위젯들 간에 중복되는 클래스에 대한 최적화(Optimization) 과정이 들어간다면 더욱 깔끔하지 되지 않을까 생각한다. html-template에 대한 것도 모두 ANT로 개발이 가능하므로 자세한 내용은 Flex Live Docs를 참고하길 바란다.

 

Flex ANT Task 활용해보기

지금까지 mxmlc 명령을 구동하기 위해 build.xml에서 <exec>를 이용했다. 이것은 매우 범용적인  방법으로 mxmlc뿐 아니라 어떤 것이든 구동하는데 쓸 수 있다. 그러나 Flex 환경에서 개발할 때 <exec>를 이용해 mxmlc를 구동 하는 것은 복잡한 설정이 들어가는 경우 관리가 힘들어지는 경우가 발생할 수 있다. 다행히 Flex SDK에서는 더욱 간편하게 mxmlc를 사용할 수 있는 기능 제공하고 있다. Flex SDK에 보면 아래 그림과 같이 ant폴더가 존재하는 것을 확인할 수 있다. ant폴더에는 Flex전용 ANT 환경을 구성해주는 Flex ANT Task가 존재한다. 소스가 공개되어 있어서 ant/src에는 Java로 만들어진 소스가 있고 ant/lib에는 flexTasks.jar가 있어 ANT구성시 이 JAR를 활용하여 Flex 전용 ANT 환경을 구성할 수 있다.

 

 

기존 build.xml에서 <properties file=./build/build/properties/> 아래에 다음 코드와 같이 <taskdef>을 추가한다. 이 부분은 Flex SDK에 있는 flexTasks.jar를 활용해 <exec> 대신 <mxmlc>, <compc> 형태로 사용할 수 있도록 한다. <target> name compile MyWidgets인 부분을 찾아 <exec>부분을 주석처리하고 대신 <mxmlc>을 삽입한다.

 

<?xml version="1.0" encoding="utf-8"?>
<project name="MyWidgets" basedir="../">

	<!-- 이 build 스크립트에서 사용할 variable 및 path 정의  -->
	<property file="./build/build.properties" />

	<!-- Flex ANT Task 사용 -->
	<taskdef resource="flexTasks.tasks" classpath="${flex3sdk.home.dir}/ant/lib/flexTasks.jar"/>
	<property name="FLEX_HOME" value="${flex3sdk.home.dir}"/>

	..... (생략) ....

	<!-- 위젯 테스터 컴파일 -->
    <target name="complie MyWidgets" depends="properties,mkdir bin">
        <mxmlc
            file="${src.dir}/MyWidgets.as"
            output="${bin.dir}/MyWidgets.swf"
        >
        	<!-- Get default compiler options. -->
        	<load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>

        	<!-- List of path elements that form the roots of ActionScript
        	class hierarchies. -->
			<source-path path-element="${FLEX_HOME}/frameworks"/>        

            <!-- List of SWC files or directories that contain SWC files. -->
            <compiler.library-path dir="${FLEX_HOME}/frameworks" append="true">
                <include name="libs" />
                <include name="../bundles/{locale}" />
            </compiler.library-path>

            <!-- Set size of output SWF file. -->
            <default-size width="500" height="600" />
        </mxmlc>
    	<!--
		<exec executable="${mxmlc.exe}" dir="${basedir}">
			<arg line="'${src.dir}/MyWidgets.as'" />
			<arg line="-o '${bin.dir}/MyWidgets.swf'" />
		</exec>
		-->
    </target>	

	..... (생략) ....

</project>

 

위 코드에서<taskdef> 아래에 <property>로 FLEX_NAME을 정의했는데 이것을 정의하지 않으면 <mxmlc>가 제대로 동작하지 않는다.

 

<mxmlc> 내부에 들어간 <load-config>, <compiler.library-path>, <default-size>등은 <mxmlc>를 구동하기 위한 설정들이다. 사실 이 프로젝트에서는 이러한 설정이 필요없다. 왜냐하면 <mxmlc>가 이러한 기본 설정을 내부적으로 대신 해주기 때문이다. 만약 설정에 변동사항이 존재한다면 이 설정들을 추가해서 수정하면 된다. 이외에도 많은 설정 옵션들이 있는데 자세한 내용은 Using Flex Ant TaskUsing the Flex Complier를 참고한다.

 

ANT를 이용해 ASDoc 만들어 보기

build.xml에 아래 코드를 추가하고 실행해보자.

 

<!-- ASDoc 만들기 -->
<target name="asdoc">
	<!-- Clean out the contents of the doc directory, without delete "docs" -->
	<!--
	<delete includeemptydirs="true">
		<fileset dir="${docs.dir}" includes="**/*" />
	</delete>
	-->
	<mkdir dir="${docs.dir}"/>

	<exec executable="${asdoc.exe}" spawn="no">
		<!-- Place the documentation in the "docs" directory -->
		<arg line="-o ${docs.dir}" />

		<!-- Specify the main source path as "src" -->
		<arg line="-sp ${src.dir}" />

		<!-- Document all of the classes in the "src" tree -->
		<arg line="-ds ${src.dir} " />

		<!-- Include the library name in the window title -->
		<arg line="-window-title 'ANT Widget Test' "/>
	</exec>
</target>

 

프로젝트내에 doc폴더가 생기고 그 안에 아래와 같이 Asdoc를 만들어진 문서를 볼 수 있을 것이다.

 

 

ANT를 이용하면 build.xml 문서 하나만 가지고 얼마나 많은 일을 한번에 처리할 수 있는가 알 수 있다.

 

 

Ant Contrib Task를 이용하여 반복 작업 줄이기

지금까지 만들어본 build.xml을 자세히 살펴보면 반복되는 작업이 있다는 것을 발견할 수 있다.  <target>의 name이 compile MyWidgets, compile widget1, compile widget2, compile widget3가 그것이다. 이들 <target>은 동일하게 mxmlc를 구동하면서 설정값도 동일하다. 이런 경우에는 하나의 <target name="compile">로 묶어주고 컴파일 대상을 번갈아가면서 <target name="compile">를 구동하게 하면 build.xml이 훨씬 간단해질 것이다. 하지만 기본 ANT와 Flex ANT Task만으로는 이런 작업이 불가능하다.

반복되는 <target>코드를 단순화 시키기 위해 주로 사용하는 것이 Ant Contrib Task이다. 이것도 Flex ANT Task와 마찬가지로 <taskdef>로 정의하여 사용할 수 있다. Ant Contrib Task는 http://ant-contrib.sourceforge.net/ 에서 제공하고 있다. 받아야하는 것은 ant-contrib-1.0b3.jar이다.

편의를 위해 아래와 같이 build에 복사해 두었다.

 

 

build.xml을 다음과 같이 수정한다.

 

<?xml version="1.0" encoding="utf-8"?>
<project name="MyWidgets" basedir="../">
	<!-- 이 build 스크립트에서 사용할 variable 및 path 정의  -->
	<property file="./build/build.properties" />

	<!-- Ant contrib 1.0b3를 사용한다 -->
	<taskdef resource="net/sf/antcontrib/antcontrib.properties">
		<classpath>
			<pathelement location="${build.dir}/ant-contrib/ant-contrib-1.0b3.jar"/>
		</classpath>
	</taskdef>		

	<!-- 해당 properties가 설정 되어 있는가 확인 -->
	<target name="properties">
		<fail unless="asdoc.exe">The "asdoc.exe" property must be set in ${build.dir}/build.properties.</fail>
		<fail unless="compc.exe">The "compc.exe" property must be set in ${build.dir}/build.properties.</fail>
		<fail unless="mxmlc.exe">The "mxmlc.exe" property must be set in ${build.dir}/build.properties.</fail>
	</target>	

	<!-- bin 폴더 생성 -->
	<target name="mkdir bin">
		<mkdir dir="${bin.dir}"/>
		<mkdir dir="${bin.dir}/widgets"/>
	</target>

	<!-- bin 폴더 삭제 -->
    <target name="clean">
        <delete includeEmptyDirs="true">
            <fileset dir="${bin.dir}"/>
        </delete>
    </target>	

	<!-- 모두 컴파일 -->
	<property name="compile_target_list" value="MyWidgets,Widget1,Widget2,Widget3" />
    <target name="compile all" depends="properties,mkdir bin">
        <foreach
            list="${compile_target_list}"
            param="compile_target"
            target="compile" />
    </target>

	<!-- 컴파일(단독구동 불가) -->
    <target name="compile">
    	<echo message="${compile_target}" />
		<exec executable="${mxmlc.exe}" dir="${basedir}">
			<arg line="'${src.dir}/${compile_target}.as'" />
			<arg line="-o '${bin.dir}/${compile_target}.swf'" />
		</exec>
    </target>	

	<!-- 위젯 테스터 실행 -->
	<target name="run MyWidgets">
		<exec executable="${flashDebugPlayer.exe}" spawn="yes">
			<arg line="${bin.dir}/MyWidgets.swf" />
		</exec>
	</target>

</project>

 

위 코드에서 <taskdef resource="net/sf/antcontrib/antcontrib.properties">부분은 Ant Contrib Task를 정의하는 부분이다. 기존 <target name="complie ...">과 같은 형태의 <target>은 모두 삭제하고 <target name="compile all">과 <target name="compile">를 만들었다. <target name="compile all">에 <foreach>를 이용해 반복하면서 MyWidgets,Widget1,Widget2,Widget3를 <target name="compile">에 대입해 구동하도록 짜여져 있다. 여기서 <foreach>는 Ant Contrib Task에서 지원하는 속성이다. 나중에 Widget4, Widget5, Widget6… 을 제작하는 일이 생기더라도 <property name="compile_target_list">value에만 추가하면 되기 때문에 작업이 훨씬 깔끔해졌다.

 

기존처럼 한개씩 컴파일하고 싶다면 build.xml에 다음 코드를 추가하면 되겠다.

 

<!-- MyWidgets 컴파일 -->
<target name="compile MyWidgets">
	<foreach list="MyWidgets" param="compile_target" target="compile" />
</target>	

<!-- widget1 컴파일 -->
<target name="compile widget1">
	<foreach list="Widget1" param="compile_target" target="compile" />
</target>	

<!-- widget2 컴파일 -->
<target name="compile widget2">
	<foreach list="Widget2" param="compile_target" target="compile" />
</target>	

<!-- widget3 컴파일 -->
<target name="compile widget3">
	<foreach list="Widget3" param="compile_target" target="compile" />
</target>

 

<target name="compile">를 그대로 사용하면서 한개씩 컴파일할 수 있도록 <foreach>를 그대로 사용했다.

Ant Contrib Task <foreach>외에도 <if>, <switch>등 유용한 것이 더 있다. 이에 대해서는 Ant Contrib Task 메뉴얼을 참고한다.

 

이렇게 기존 ANT를 보완하는 Ant Contrib Task를 사용함으로써 build.xml을 더욱 깔끔하게 만들 수 있게 되었다.


정리하기

지금까지 Flex Builder 3에서 ANT를 활용하는 방법에 대해서 간단한 예제를 통해 살펴보았다.  AS3 프로젝트가 아닌 라이브러리 프로젝트나 Flex프로젝트등에도 ANT가 매우 유용하게 쓰여질 수 있다는 것을 충분히 이해했다면 이 글의 목표를 달성한 것이라 생각한다.

 

Flex ANT를 최고의 학습 방법은 경험이다. 스스로 해당 자료를 찾아 직접해보는 것이 가장 중요하다.

Google Code Open Source Flex 프로젝트들이 꽤 있는데 SVN으로 Flex Builder에 등록해서 사용하다 보면 때때로 ANT를 사용한 소스를 접해볼 수 있다.  Flex ANT가 아니라 일반 ANT긴 하지만 as3corelib as3flexunitlib등이 그 예가 아닐까 생각한다.

 

실제로 실전 프로젝트에서 ANT는 매우 유용하다. 익혀두고 적절히 활용하면 몸과 마음이 편해지는 좋은 기술이겠다.

 

관련글

 

 

옥상훈님이 운영하시는 개발자 모임인 OkGosu.Net에서 Adobe RIA관련 커뮤니티 세미나를 엽니다.

자세한 내용은 아래와 같습니다.

소개 :
OkGosu.Net은 지난 2008년 9월1일에 오픈한 커뮤니티로 회원수는 700여명에 달하며 Flex관련 스터디를 하는 개발자의 모임이다. Flex를 비롯하여 RIA와 UX관한 최신 뉴스, 온라인 질문 답변 그리고 정기적인 세미나 활동을 주로 하고 있다.

 

시간 :
2009년 5월 23일(토) 13:30 ~ 17:30

 

장소 :
서울특별시 서초구 서초동 비트아카데미 멀티미디어관 지하 2층

 

강의내용 :
13:30 ~ 14:00 접수 및 입장완료
14:00 ~ 14:40 플렉스로 구글 가젯 (위젯) 만들기 - 옥상훈
14:50 ~ 15:30 자바스크립트로 만드는 매쉬업 Adobe AIR 애플리케이션 이야기 - 오창훈
15:40 ~ 16:20 스타플 서비스에서 사용된 Flex 기술 - 지용호
16:30 ~ 17:10 플래시 플랫폼으로 구현하는 FaceBook 매쉬업 애플리케이션 개발
                   - 이정웅
17:10 ~ 17:30 폐회사

 

강사 :
옥상훈, 오창훈, 지용호, 이정웅

 

참가비 :
무료

 

저는 스타플 서비스에 사용한 Flex 기술이라는 제목으로 발표할 예정입니다. 강의내용을 어떻게 정해야할지 모르겠는데요. 개발자들 모임이니 기술적인 내용을 원하겠죠? 혹시라도 궁금한 사항을 댓글에 적어주신다면 반영해서 준비하도록 하겠습니다.

 

사전접수는 아래 링크에서 하시면 됩니다.

 

사전접수 : http://www.devmento.co.kr/dtfe/conference/register/register_form.jsp?cseq_no=9

이외에도 5월에는 다양한 행사를 하네요.

아래 열이아빠님의 글을 참고하시면 도움이 될 것 입니다.

 

5월에 다채로운 RIA와 관련된 이야기를 만나보세요

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com)

스타플(http://starpl.com) 서비스가 일부 리뉴얼을 했다. 간단하게 리뉴얼 내용을 소개하고자 한다.

 

1. 별지도의 우주여행


 

스타플은 별지도를 주요 기반으로 삼는다. 기존에는 수억개의 별과 회원들의 별만을 보여주어서 다소 검은화면에 점이 있다는 느낌밖에 주지 못했다. 조금 더 우주의 느낌을 주기 위해 109개 먼하늘의 물체(성운, 성단, 은하)를 별지도에 담았다. 우측상단에 있는 미니맵의 우주여행 버튼을 눌러 안드로메다 은하, 플라이아데스 산개성단등 유명한 먼하늘 물체로의 여행을 할 수 있다. 첫번째 버전이라 대상도 그리 많지 않고 움직임도 부드럽지 못하긴 하다. 다음으로 태양계에 속한 태양, 달, 행성들(수성, 금성, 화성, 목성 등)도 표현할 것이다. 더욱더 나아가 혜성, 유성우와 같은 것도 표현하려고 계획중이다.

 

 

우주여행 개발이야기

서비스 초반에는 별지도에 성운,성단,은하등이 들어가면 너무 학술적인 프로그램으로 비춰질 것 같아 만들지 않았다.  대신 별지도에 SNS적인 느낌을 주기 위해 수많은 고민을 했다. 하지만 공간으로 표현하는 SNS는 말로만 쉽지 표현하는 것은 그리 쉽지 않았다. 표현할 수 있다고 해도 과연 사용자에게 편의성과 즐거움을 줄 수 있을까 미지수였다. 즉, 개발노력 대비 효과가 미비할 수 있다는 판단이 있었다.

 

별지도는 별지도가 되야한다는 생각이 들기 시작했다. 별지도는 밤하늘의 느낌을 줄 수 있어야 한다고 생각했고 그것이 사용자에게 더욱 자극제가 될 수 있을거라 여겨졌다. 결국 1차적으로 성운,성단,은하를 별지도에 보여줄 수 있도록 했다.  이러한 시도는 꽤 흥미롭고 사용자들에게도 좋은 경험을 줄 수 있을거라 생각한다.

앞으로 태양계와 더 많은 천체들을 보여주고 관련 정보까지 보여주기 위한 계획을 하고 있다. 또한 기존 별지도가 Flex로 개발되어 무거운데 더욱 가볍게 ActionScript 3.0 기반으로 제작하여 사용자들에게 더욱 좋은 퍼포먼스를 보일 수 있도록  준비할 것이다.

 

 

2. 새로운 위젯 : 시계, 최근이미지, 달력

스타플에서는 시계위젯, 최근이미지위젯, 달력위젯을 새로 추가했다. 스타플의 별마트에 가면 별가루(활동량에 따라 얻어지는 가상화폐)로 구입한 후 내 별에 꾸밀 수 있다.

 

 

시계 위젯은 아날로그와 디지털 시계로 구분된다. 자유롭게 디자인을 골라서 구입하면 되겠다.

 

 

최근이미지 위젯은 자신이 쓴 글의 사진들을 모아 자동으로 붙여주는 역할을 하며 카테고리를 선택하여 해당 카테고리내에 있는 사진만 선별해서 보여줄 수 있다. 사진을 클릭하면 해당 포스트로 이동한다.

 

달력은 일반달력과 월/일만 표시하는 달력, 일자로 보여지는 달력등이 있다.  앞으로 음력/양력, 해당포스트 연결 등의 기능도 들어가지 않을까 생각한다.

 

위 화면은 새로운 위젯을 적용한 필자의 별(http://starpl.com/jidolstar) 이다.

 

 

위젯시스템 개발이야기

 

위젯시스템은 예전 Flex Module을 이용한 것을 걷어내고 순수 ActionScript 3.0 기반으로 수정했다. 그 이유는 Flex Module는 Flex Application과 거의 동일한 용량을 가진다. Flex Module은 매우 유용하지만 스타플 별지도에는 Module로 만들어진 많은 위젯을 올려야하므로 매우 무거웠다. Module을 버리고 ActionScript 3.0 기반으로 제작한 결과 1/10~1/50배 이상의 위젯크기를 줄일 수 있었고 로드 실패율도 현격히 줄일 수 있게 되었다. 물론 로드속도도 크게 개선되었다.

 

앞으로 별꾸미기, 타임라인, 별지도도 Flex를 걷어내고 ActionScript 3.0기반으로 제작하여 더욱 가볍게 만들려고 한다.

 

 

3. 개편된 알림이 시스템 : 스타플(인기기록, 와글댓글, 새기록) 탭 추가

 

기존에는 스타플 메인화면에서만 인기기록, 와글댓글, 새기록을 볼 수 있었다. 이 기능을 알림이 시스템에 포함시킴으로써 더욱더 스타플 컨텐츠를 접근하기 편하도록 꾸몄다. 더욱이 로그인하지 않아도 볼 수 있게 되었다. 또한 이전에 말풍선만 여러개 나와 복잡했던 UI를 깔끔하게 리스트 형태로 볼 수 있게 되었다.

 

보여지는 말풍선도 긴 제목이 잘리는 어색함을 극복하도록 여러줄로 제목표시할 수 있게 UI개선이 있었다.

 

 

위처럼 알림이를 통해 로그인을 할 수 있도록 기능을 개선했다. 이로써 스타플 사용자는 이전보다 더욱 편하게 스타플을 이용할 수 있게 되었다.

 

 

알림이 서비스 개발 이야기

필자는 알림이 서비스 개발에 별지도에 표현되는 말풍선 부분 및 서버사이드 통신 부분에만 참여했다. 스타플이 정식 오픈한 것은 2008년 12월 2일이였다. 그전에는 close베타로 여러시도를 했었다. 그중에 이 알림이 기능은 스타플에서 내놓은 매우 훌륭한 기능이라 생각한다. 처음부터 알림이가 좋은 기능을 갖춘 것은 아니였다. UI편의성, 속도, 디자인등 고민을 여러번 거듭한 끝에 만들어진 결과물이다. 이번에 인기기록, 와글댓글, 새기록이 알림이에 추가함으로써 그 완성도를 높힐 수 있게 되었다고 여겨진다.

 

4. 별바로가기 기능 : 친구별과 관심별에 직접 찾아가요.



기존에 없었던 기능으로 로그인을 하면 우측상단 네비게이션바에 “별바로가기”가 보여진다. 이것으로 기존 별친구나 관심별로 쉽게 찾아갈 수 있으며 친구찾기, 친구초대도 한두번의 클릭만으로도 쉽게 할 수 있게 되었다.

스타플 개발이야기

스타플(http://starpl.com)의 독특한 UI구조(Flash가 전체로 깔려있고 그 위에 HTML DIV가 올라옴)는 개발에 많은 어려움을 주었다. 특히나 속도문제는 지금도 중요한 이슈이다. 보통 RIA사이트의 경우 Flex로 만들어졌다고 할때 그 위에 HTML이 올라오는 경우는 거의 없다. Flash/Flex 애플리케이션을 브라우저에 Embed할때 wmode라는 것을 지정한다. 크게 투명(transparent)와 불투명(opaque)로 지정할 수 있다. wmode를 불투명으로 지정하는 경우 Flash/Flex 애플리케이션 위에 HTML DIV를 올릴 수 없다. 반대로 wmode를 투명(transparent)으로 지정하면 Flex/Flash 애플리케이션 위에 HTML문서를 올릴 수 있다. 투명처리했다는 것은 Flash Player가 그림을 그리기 위해 자신의 SWF컨텐츠를 렌더링하는 것외에도 SWF위에 다른 무언가가 가려지는 것도 고려한다는 것을 의미한다. 당연히 이로 인해 전체적인 퍼포먼스가 떨어질 수 밖에 없다. 이 때문에 그냥 단순한 더하기가 아닌 곱하기 단위로 퍼포먼스 하락을 보인다.

 

스타플의 구조는 wmode를 투명으로 지정할 수 밖에 없는 구조이다.  앞서 설명한 이유 때문에 순수 Ajax 또는 순수 Flash로 만들어진 다른 서비스에 비해 더 느려진다. 이를 극복하는 방법은  별지도, 타임라인을 더욱 가볍고 빠르게 동작하도록 만들 수 밖에 없다. 메모리와 CPU연산을 최대로 아끼도록 최적화 해야한다. 그래서 Flex를 걷어내고 순수하게 ActionScript 3.0으로만 제작하려고 계획중이다. 개발의 편의성을 제공하는 Flex SDK의 프레임워크를 버린다는 것은 필자에게 큰 부담감으로 작용한다. 왜냐하면 Flex 프레임워크에서 제공하는 데이터 통신, 컴포넌트, 이펙트, MXML의 편의성, CSS, RSL등의 기능을 전부 쓰지 못하기 때문이다.

 

스타플 홍보 동영상

스타플

 

정리하며

스타플은 아직도 배고프다. 지금도 시도해보지 못한 기획이 많다. 이제 우리가 생각할 수 있는 것의 10% 했는가? 아직 완성된 서비스가 아니라는 것이다. 비록 많은 사람이 참여해서 만든 서비스는 아니지만 천천히 완성도를 높여가는 모습을 보면서 서비스의 성공을 더욱 확신하게 된다.

 

별을 메타포(상징)으로 삼는 스타플은 앞으로 우주 기반 대한민국 대표 SNS가 될 것이라 생각하고 계속 정진해 나갈 것이다.

 

관련글

 

ActionScript 3.0은 스크립트 언어이기 때문에 어떻게 코딩을 하느냐에 따라 퍼포먼스에 크게 영향을 줄 수 있다. 다음 글에서 최적화 코딩 기술에 대해 언급하고 있으니 참고하면 좋겠다.

 

ActionScript 3.0 and Flex optimization techniques and practices

 

글쓴이 : 지돌스타(http://jidolstar.com/blog)

도전! 개발자 무한 상상력

 

AIR 애플리케이션 경연대회

 

Adobe AIR는 웹과 데스크톱의 한계를 넘어, 개발자 여러분께 무한 상상력을 발휘할 수 있는 플랫폼 환경을 제공합니다. 지금 AIR 애플리케이션 경연대회에 참여해 여러분의 무한 상상력을 뽐내 보세요. 7분을 선정해 푸짐한 경품을 드립니다.

 

 

<경품>

  • 대상(1팀) : Adobe Flash CS4 Professional 1copy, 상장 수여
  • 최우수상(1팀) : Adobe Flex Builder 3 Professional 1copy, 상장 수여
  • 우수상(1팀) : Filco Majestouch 기계식 키보드, 상장 수여
  • 장려상(4팀) : 우야꼬의 Flash CS4로 만드는 AIR 1.5 서적, 상장 수여

 

<특전>

입상한 7팀 중 대상 1팀과 최우수상 1팀에게는 2010년 ACC(Adobe Community Champion) 후보가 될수 있는 자격을 부여해 드립니다.

 

 <일정> 

  • 선정기준 : 응모된 작품 중 ACC(Adobe Community Champion)이 심사해 7건을 선정
  • 마감 : 2009년 5월 17일까지
  • 입상작 발표 :  2009년 5월 25일
  • 현장심사, 발표 : 2009년 5월 28일

 

<응모 방법>   

  1. AIR 애플리케이션 제작을 위한 자료를 학습한다.
    <자료>
    [동영상] 작년 대상 수상자의 “AIR 애플리케이션 제작기”
    [PDF] "AIR로 업무를 혁신하다" AIR 국내,해외통합본 PDF 제공
    - 온라인미디어 분야 - Verizon Wireless
    - 방송 분야 -  영국 BBC의 iPlayer
    - 그룹웨어 적용 – 희림건축 AIR기반의 그룹웨어 구축 사례
    - 대고객서비스 - FedEx, FOX News, eBay
    - 문서관리 솔루션 –  LiveCycle AIR Desktop 애플리케이션 소개
  2. AIR 애플리케이션 기획의도와 제작과정을 캡처한 스케치, 프로그램,완성작품 시연동영상을 첨부해 메일(admin@adoberia.co.kr)로 응모한다.

 

<선정 과정>

  1. 접수된 작품 중 ACC 사전심사를 실시해 1차 7팀의 작품을 선정한다.
  2. 7팀의 작품을 가지고 최종 발표회 심사를 실시해 대상을 선정한다.

 

<심사기준>

  • 작품의 기술성(40)
    -         데스크톱 리소스 응용도(20) : AIR의 장점인 데스크톱의 리소스를 효과적으로 응용하는가?
    -         성능(10) : 애플리케이션의 성능이 이용에 불편없는가?
    -         완성도(10) : 구현하고자 하는 기능과 제출문서 등이 모두 완성되었는가?
  • 주제의 창의성(20) : 기존 애플리케이션과 주제면에서 차별화되는가?
  • 작품의 활용성(20) : 사용자의 생활에 도움을 주는 애플리케이션인가?
  • 사용자 편의성(20) : 사용자가 이용하는데 효과적인 UI로 구성되었는가?
  • 기타 궁금하신 점은 블로그 댓글이나 메일( admin@adoberia.co.kr )을 이용해주세요.

 

궁금합니다.

이벤트 공지가 나간 후 문의메일이 들어오고 있습니다.

모두들 궁금해 하실 듯하여 블로그에 올립니다.

 

소스코드도 제출하나요?

네. 메일( admin@adoberia.co.kr )로 접수하시면 됩니다. 용량이 클 경우에는 메일로 문의해주세요.

 

 

'완성 작품 시연동영상'을 제출해야 하는데요. 꼭 제출해야하나요?

완성 작품 시연동영상은 선택사항입니다.


자신의 작품을 직접 시연할 수 없기 때문에,  심사시 작품을 100% 보여주지 못합니다. 이런 점을 방지하고자, 작품을 최대한 표현할 수 있는 방법으로 완성 작품 시연동영상을 선택하였습니다.


동영상은 3분내외의 작품을 시연하는 영상입니다. 동영상의 품질이나 효과 등은 심사시 평가되지 않습니다.

 

 

입상하게 되면 어떤 특전이 있나요.

상품과 상장이 수여되고, 대상과 최우수상에 한하여 2010년 ACC 후보가 될 수 있는 자격을 부여해드립니다. 또한, 입상작은 AIR Badge로 제작하여, Adobe RIA공식사이트 AIR 샘플 애플리케이션으로 소개될 예정입니다.

 

 

출처 : 한국 Adobe RIA 공식 사이트

Adobe Stratus는 Flash Player 10이상 또는 AIR 1.5 이상에서 RTMFP(Real Time Media Flow Protocol)이라 불리우는 통신프로토콜을 이용하여 클라이언트끼리 Peer to Peer(P2P)가 가능하게한 기술이다. TCP가 아닌 UTP를 기반으로 하기 때문에 통신 속도에는 좋지만 데이타 전송에 대한 신뢰도는 떨어지지 않을까 판단한다. 이러한 이유로 Stratus는 채팅, 각종 미디어 통신, 멀티 게임등을 만드는데 적합하다고 할 수 있다. 

 

위 그림에서 볼 수 있듯이 기존 RTMP는 사용자가 서버를 통해서만 다른 사용자에게 접근이 가능했다. 하지만 Adobe Stratus에서 제공하는 RTMFP를 이용하면 처음 Staratus 서버와 접속하여 인증절차만 완료하면 Flash Player 끼리 P2P통신이 가능해진다. 이렇게 함으로서 서버의 부하를 줄일 수 있게 되었다.

필자는 Adobe에 올라온 예제를 가지고 테스트를 해봤다. 이 예제는 Adobe Stratus를 가지고 어떤 기능까지 가능한가 총체적으로 보여주는 예제이다. 단순한 채팅뿐 아니라 마이크, 스피커, 비디오 예제가 한 소스에 들어가 있다. 예제는 [여기]에서 실행해볼 수 있고 또한 다운로드 받을 수 있다. 

필자는 제공된 Stratus 예제 Flex 애플리케이션을 수정하지 않고 서버측 사용자 등록하는 코드인 reg.cgi를 PHP로만 포팅해서 테스트 해봤다. 

이것을 테스트 하기 위해 다음단계를 거친다. 

  1. Flash Player 10 이상 설치한다.
  2. Flex SDK 3.2 또는 Flash Builder 3.0.2 를 설치한다.
  3. Stratus beta 개발 키를 받는다. Adobe에 가입해야한다.
  4. 참고로 Flash Player 10 API 문서를 본다. (한글은 여기)
  5. “Stratus service for developing end-to-end applications using RTMFP in Flash Player 10” 문서를 본다. 우야꼬님이 번역한 문서는 [여기]를 참고
  6. 예제 프로그램 다운로드
  7. 질문 및 답변은 여기로

 

Stratus는 현재 Adobe측에 서버를 두고 있고 공개하고 있진 않다. 그러므로 자신의 서버에 Stratus를 설치하지는 못한다. 하지만 개발키를 부여하고 있기 때문에 “rtmfp://stratus.adobe.com/개발키” 에 접속해서 언제든지 개발은 가능하다. 아래는 개발키를 가지고 Adobe의 Stratus서버에 접속인증을 하는 예제이다.

 

private const StratusAddress:String = "rtmfp://stratus.adobe.com";
private const DeveloperKey:String = "your-developer-key";
private var netConnection:NetConnection;

netConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS,
netConnectionHandler);
netConnection.connect(StratusAddress + "/" + DeveloperKey);

 

다운로드 받은 예제에서 사용자 등록 웹서비스를 위한 reg.cgi는 python으로 만들어졌다. python에 대해서 아는바가 별로 없어서 이 코드를 PHP에서 동일하게 동작하도록 만들었다. 또한 DB는 간편하게 만들기 위해 SQLite를 이용한다. 

먼저 개인 리눅스 서버에 sqlite를 설치했다.(안녕리눅스 pkgadd를 이용해서 간편하게 설치함 ^^). 다른 환경이라면 그 환경에 맞게 설치하면 되겠다.(http://www.sqlite.org/ 에서 다운로드 받아 설치한다.)

 

# pkgadd sqlite

Zend Optimizer requires Zend Engine API version 220051025.

The Zend Engine API version 220060519 which is installed, is newer.

Contact Zend Technologies at http://www.zend.com/ for a later version of Zend Optimizer.

sqlite          : Install 성공

 

sqlite는 아래처럼 mysql과 비슷하게 사용이 가능하다.

# sqlite test.db

SQLite version 2.8.17

Enter ".help" for instructions

sqlite> create table test(idx integer);

sqlite> .table

test

sqlite> insert into test(idx) values(1);

sqlite> insert into test(idx) values(2);

sqlite> insert into test(idx) values(3);
sqlite> select * from test;

1

2

3

sqlite> .quit

 

여기서 사용할 DB를 만들기 위해 아래와 같이 원하는 폴더에 staratus_registration.db를 만들자. 만들고 그냥 저장하면 된다.

 

vi staratus_registration.db

 

아래는 만들어진 SQLite DB(staratus_registration.db )를 이용해 사용자 접속 처리를 하는 PHP(reg.php) 코드이다. 참고로 PHP 5.2 이상 환경이고 이 환경에서는 SQLite에 대한 API를 제공하고 있다.

 

<?php
	$_METHOD = $_GET; // $_GET 또는 $_POST;
    $username = $_METHOD['username'];
	$identity = $_METHOD['identity'];
	$friends = $_METHOD['friends'];

    header("Content-type:text/plain;charset=utf-8");
    header("Cache-Control: no-cache, must-revalidate");
    header("Pragma: no-cache");

	echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
	echo "<result>";

	//DB 열기
	$db = @sqlite_open('stratus_registrations.db', 0777, $error);
	if( $db )
	{
		//Table 존재 확인
		$result = @sqlite_query($db, "SELECT name FROM sqlite_master WHERE type='table' AND name='registrations'");
		$tables = @sqlite_fetch_all( $result );

		$exist_table = true;

		//테이블이 존재하지 않는다면 테이블을 생성함
		if( count($tables) == 0 )
		{
			//Table 생성
			$query = "CREATE TABLE registrations (
							m_username VARCHAR COLLATE NOCASE,
							m_identity VARCHAR,
							m_updatetime DATETIME,
							PRIMARY KEY (m_username) );
					CREATE INDEX registrations_updatetime ON registrations (m_updatetime ASC);";
			if( !@sqlite_exec($db, $query, $error) )
			{
				echo "<error>$error</error>";
				$exist_table = false;
			}
		}

		//Table이 존재하는 경우만 실행
		if( $exist_table )
		{
			//개인접속
			if( $username )
			{
				//기존 사용자 있는지 확인
				$select_query = "SELECT m_username, m_identity FROM registrations WHERE m_username = '$username'";
				$result = @sqlite_query($db, $select_query);
				$array = @sqlite_fetch_array($result);

				//사용자 추가 및 업데이트
				if( $array )
				{
					$update_query = "update registrations SET m_identity='$identity', m_updatetime=datetime('now') where m_username='$username';";
					//echo "<update_query>$update_query</update_query>";
					if( sqlite_exec($db, $update_query,  $error) )
					{
						echo "<update>true</update>";
					}
					else
					{
						echo "<update>false</update>";
						echo "<error>$error</error>";
					}
				}
				else
				{
					$insert_query = "insert into registrations (m_username, m_identity, m_updatetime) values ( '$username', '$identity', datetime('now'))";
					//echo "<insert_query>$insert_query</insert_query>";
					if( sqlite_exec($db, $insert_query, $error) )
					{
						echo "<update>true</update>";
					}
					else
					{
						echo "<update>false</update>";
						echo "<error>$error</error>";
					}
				}
			}
			//친구연결요청(1시간 동안 접속을 하지 않은 사람은 무시)
			else if( $friends )
			{
				$f = trim($friends);
				echo "<friend><user>$f</user>";
				$select_query = "SELECT m_identity from registrations where m_username = '$f' and m_updatetime > datetime('now', '-1 hour')";
				$result = @sqlite_query( $db, $select_query );
				$array = @sqlite_fetch_array($result);
				if( $array )
				{
					echo "<identity>$array[m_identity]</identity>";
				}
				echo "</friend>";

			}

		}
	}
	else
	{
		echo "<error>$error</error>";
	}

	echo "</result>";
?>

 

“http://당신의 Domain/경로/reg.php?user=jidolstar&identity=발급받은개발키” 로 접속해보자. 아래와 같이 나오면 정상이다. 이것은 처음 사용자가 접속했을때 인증하기 위한 것이다.

 

에러가 발생하면 PHP파일과 DB가 같은 폴더에 있는지 확인하고 그래도 문제가 있다면 DB를 포함하는 폴더의 권한을 777로 바꿔보자.

 

 

이번에는 http://당신의 Domain/경로/reg.php?friends=jidolstar 로 접속해보자. 아래와 같이 나오면 정상이다. 이것은 상대방이 접속요청을 할때 인증하기 위한 것이다.

 

 

다운 받은 Flex코드를 새 Flex 프로젝트를 만들어 복사한뒤 Application내에 개발키와 웹서비스 URL를 설정한뒤 실행한다.

 

// stratus address, hosted by Adobe
[Bindable] private var connectUrl:String = "rtmfp://stratus.adobe.com"; 

// developer key, please insert your developer key here
private const DeveloperKey:String = "Adobe에서 발급받은 개발키"; 

// please insert your webservice URL here for exchanging
private const WebServiceUrl:String = "http://당신의 URL/설치디렉토리/reg.php";

 

성공한다면 아래처럼 시원하게 채팅이 가능할거다. 물론 웹캠과 마이크가 있다면 더욱 완벽한 테스트가 가능하다.

 

 

필자가 못생겨서 더이상 읽고 싶지 않나? 할 수 없다. 이미 더 이상 쓸 말도 없다. ㅋ

 

참고

 

이 글은 ActionScript 3.0 프로젝트에서 BlazeDS와 통신하는 환경을 배경으로 한다.  Flex MXML를 배제하고 단지 Flex에 제공되는 RPC, 리모팅 관련 클래스를 사용하고자 한다. Flex MXML를 배제하는 이유는 다음과 같다.

 

Flex SDK의 Visual 컴포넌트들은 일반 솔루션 제품을 개발하는데 매우 유용하지만 어떤 경우에는 프로그램의 용량과 퍼포먼스 향상에는 도움이 안될 수 있다. 가령, Flex SDK에서 제공하는 Button과 List만 사용한다고 가정하자. 이 컴포넌트만 사용하는데도 불구하고 Flex 컴파일러는 그와 관련된 클래스까지 함께 컴파일해서 프로그램 용량을 크게 한다. 또한 Visual 컴포넌트들은 커다란 프레임워크로 만들어져 있기 때문에 Sprite만을 이용해서 만든 프로그램보다 퍼포먼스가 떨어진다.

 

물론 Flex에서 제공하는 컴포넌트를 적극적으로 다양하게 사용할 필요가 있다면 개발 효율성 및 유지보수 측면에서 Flex 프로젝트로 개발해야한다. 하지만 겨우 컴포넌트 몇개만 가져다가 사용하는 경우에는 Flex로 개발하는 것이 오히려 프로그램의 질을 떨어뜨릴 수 있는 결과를 초래할 수 있게 된다.(Flex가 나쁘다는 것이 아니라 필요에 맞게 사용해야한다는 것을 언급하는 것임을 강조한다.)  

 

Flex 환경에서 AMF3 통신

 

다음 예를 보자.

 

아래처럼 Flex 프로젝트로 만들어 Release 버전으로 컴파일 하게 되면 262kb가 나온다. 매우 단순한 프로그램인데도 불구하고 용량이 꽤 크다. 이유는 앞서 설명했다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
	<mx:RemoteObject id="ro" destination="my-dest" showBusyCursor="true">
		<mx:method
			name="getData"
			result="returnHandler(event)"
			fault="mx.controls.Alert.show(event.fault.faultString)"/>
	</mx:RemoteObject>
	<mx:Button name="load" click="load()"/>
	 <mx:Script>
	<![CDATA[
		import mx.rpc.events.ResultEvent;

		private function returnHandler(e:ResultEvent):void
		{
			trace( e.result );
		}

		private function load():void
		{
			ro.getData( 1, 2 );
		}
	]]>
	</mx:Script>
</mx:Application>

 

 

AMF3 직렬화 처리가 안된 ActionScript 3.0 프로젝트 개발

 

AMF3 통신만 원한다면 앞서 설명한 이유 때문에 굳이 Flex로 개발할 필요가 없다. ActionScript 3.0 프로젝트 환경에서도 충분히 AMF 통신이 가능하기 때문이다. 아래는 OpenAMF나 PHPAMF와 같은 서버측 프로그램을 이용할 때 ActionScript 3.0 코드이다.

 

package {
	import flash.display.Sprite;
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	import flash.net.Responder;
	import flash.net.registerClassAlias;

	public class AS3NetConnectionTest extends Sprite
	{
		private var nc:NetConnection;

		public function AS3NetConnectionTest()
		{
			//create a netconnection
			nc = new NetConnection();
			nc.objectEncoding = ObjectEncoding.AMF3;
			nc.connect("http://192.168.*.*/_FLEX/amf");

			//create a responder
			var responder:Responder = new Responder(result, fault);

			//make the call
			nc.call("com.jidolstar.service.MyClass.getData", responder, 2);
		}

		private function result( data:Object ):void
		{
			trace("RPC Ok");
		}

		private function fault( event:Object ):void {
			 trace("RPC Fail");
			 trace(ObjectUtil.toString(event););
		}
	}
}

 

위 코드는 Flex에 관련된 것이 없고 단순히 flash.* 패키지 기반의 순수 ActionScript 3.0 환경에서 개발한 것이다. 용량도 Release로 컴파일하면 1kb도 안된다. Flex로 개발할때와 거의 250배 이상 용량차이를 보인다.

 

 

AMF3 직렬화 처리가 된 ActionScript 3.0 프로젝트 개발

 

하지만 BlazeDS, LCDS 서버의 리모트 객체와 클라이언트 객체간에 직렬화(serialization)가 필요한 경우 위 ActionScript 코드만으로는 해결할 수 없다. 이 문제는 본인의 경우 기존 프로젝트에서 BlazeDS로 만들어진 서버측의 코드를 변경하지 않고 Flex대신 ActionScript 3.0로 개발할때 문제가 발생했다. Flex Builder에서 ActionScript 3.0 프로젝트로 하면 playerglobal.swc, flex.swc, utilites.swc만 라이브러리로 등록되어 있기 때문에 BlazeDS에서 AMF3를 통해 넘겨주는 message관련 객체나 ArrayCollection 객체등을 직렬화 처리를 할 수 없게 된다. 왜냐하면 이들 3개 swc에는 이와 관련된 클래스가 정의되어 있지 않기 때문이다.

 

BlazeDS의 message관련 객체에 대한 직렬화를 위해서는 Flex SDK에 있는 rpc.swc가 필요하다. 또 ArrayCollection 직렬화를 위해서는 framework.swc 가 필요하다. 그럼 Flex를 사용하는 것과 무엇이 다르냐라고 반문할 수 있다.  그러나 ActionScript 3.0 기반이기 때문에 framework.swc에 정의된 비주얼 컴포넌트를 전부 사용하지 않고 단지 ArrayCollection과 그와 관련된 클래스만 사용하기 때문에 이것만으로도 프로그램의 용량을 획기적으로 줄이게 된다.

 

아래에서 설명하는 대로 프로젝트 환경을 구성해보자.

 

 

1. Flex Builder 3 Professional에서 ActionScript 3.0 프로젝트를 만든다.

 

2. 프로젝트에 Flex SDK에 있는 rpc.swc, rpc_rb.swc, framework.swc, framework_rb.swc를 라이브러리로 추가한다.

 

참고로, SWC는 윈도우의 경우 C:/Program Files/Adobe/Flex Builder 3/sdks/SDK버전/frameworks/에 libs 폴더와 locale 폴더에 있다. SWC에서 _rb라고 붙은 것은 resource bundle의 약어이다. Flex는 리소스 번들을 지원해주는데 우리의 목적과는 상관없는 것이지만 컴파일을 위해서는 추가해주어야 에러가 없이 컴파일이 된다.

 

 

3. 아래처럼 컴파일러 옵션에 -locale=en_US -include-resource-bundles=collections,rpc,messaging 를 추가한다.

 

 

4. 아래 코드처럼 코딩한다. (서버측 BlazeDS 설정은 여기서 설명을 생략한다.)

 

package {
	import com.jidolstar.vo.Class1;
	import com.jidolstar.vo.Class2;

	import flash.display.Sprite;
	import flash.net.registerClassAlias;

	import mx.collections.ArrayCollection;
	import mx.collections.ArrayList;
	import mx.core.mx_internal;
	import mx.logging.targets.TraceTarget;
	import mx.messaging.ChannelSet;
	import mx.messaging.channels.AMFChannel;
	import mx.messaging.config.ConfigMap;
	import mx.messaging.config.LoaderConfig;
	import mx.messaging.events.*;
	import mx.messaging.messages.*;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.rpc.remoting.RemoteObject;
	import mx.utils.ObjectProxy;

	use namespace mx_internal;

	/**
	 * RemoteObject 이용
	 */
	public class AS3RemoteObjectTest extends Sprite
	{
		public function AS3RemoteObjectTest()
		{

			registerClassAlias("flex.messaging.messages.CommandMessage",CommandMessage);
			registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
			registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
			registerClassAlias("flex.messaging.io.ArrayList", ArrayList);
			registerClassAlias("flex.messaging.config.ConfigMap",ConfigMap);
			registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);
			registerClassAlias("flex.messaging.io.ObjectProxy",ObjectProxy);
			registerClassAlias("flex.messaging.messages.HTTPMessage",HTTPRequestMessage);
			registerClassAlias("flex.messaging.messages.SOAPMessage",SOAPMessage);
			registerClassAlias("flex.messaging.messages.AsyncMessage",AsyncMessage);
			registerClassAlias("flex.messaging.messages.MessagePerformanceInfo", MessagePerformanceInfo);
			registerClassAlias("DSA", AsyncMessageExt);
			registerClassAlias("DSC", CommandMessageExt);
			registerClassAlias("DSK", AcknowledgeMessageExt);

			registerClassAlias("com.jidolstar.service.domain.Class1", Class1 );
			registerClassAlias("com.jidolstar.service.domain.Class2", Class2);

			//Sets up some more descriptive tracing on the client
			var target:TraceTarget = new TraceTarget();
			target.level = 0;

			//This is needed so the Flex libraries have access to the root objects properties
			var swfURL:String = this.loaderInfo.url;
			LoaderConfig.mx_internal::_url = this.loaderInfo.url;
			LoaderConfig.mx_internal::_parameters = this.loaderInfo.parameters;

			//This is the channel definition
			//This tells RemoteObject where to go
			var amfChannel:AMFChannel = new AMFChannel( "my-amf", "http://192.168.0.17/amf" );
			amfChannel.requestTimeout = 3;
			amfChannel.connectTimeout = 3;
			amfChannel.addEventListener(ChannelFaultEvent.FAULT, handleChannelFault);
			amfChannel.addEventListener(ChannelEvent.CONNECT, handleChannelConnect);
			amfChannel.addEventListener(ChannelEvent.DISCONNECT, handleChannelDisconnect);

			var channelSet:ChannelSet = new ChannelSet();
			channelSet.addChannel( amfChannel );

			//Make sure you include the right RemoteObject at
			//import mx.rpc.remoting.RemoteObject;
			//not
			//import  mx.rpc.remoting.mxml.RemoteObject;
			var ro:RemoteObject = new RemoteObject;
			ro.destination = "mydest";
			ro.channelSet = channelSet;
			ro.addEventListener(ResultEvent.RESULT, onResult );
			ro.addEventListener(FaultEvent.FAULT, onFault );
			ro.getData( 1, "jidolstar" );
		}

		public function onResult(event:ResultEvent):void
		{
			trace("RPC Ok");
			trace(event.result);

		}

		public function onFault(event:FaultEvent):void
		{
			trace("RPC Fail");
        	trace(event.message);
		}

		public function handleChannelFault(e:ChannelFaultEvent):void {
		    trace("Channel Fault");
			trace(e);
		}		

		public function handleChannelConnect(e:ChannelEvent):void {
		    trace("Channel Connect");
			trace(e);
		}

		public function handleChannelDisconnect(e:ChannelEvent):void {
		    trace("Channel Disconnect");
			trace(e);
		}

	}
}

 

위 코드는 BlazeDS나 LCDS 환경에서 RPC기반인 RemoteObject로 AMF3 통신처리를 하는 ActionScript 3.0 프로젝트 예제이다.

 

registerClassAlias()는 BlazeDS에서 오는 AMF3 메시지 객체를 ActionScript 3.0으로 직렬화처리를 위한 것이다. 이 함수에 message관련 클래스와 ArrayCollection이 등록되어 있는 것을 확인하기 바란다. 이것으로서 ActionScript 3.0 프로젝트를 통해서도 충분히 ArrayCollection을 쓸 수 있게 된다.

 

registerClassAlias("com.jidolstar.service.domain.Class1", Class1 )는 서버측에 정의된 com.jidolstar.service.domain.Class1 객체를 ActionScript 3.0에 정의된 com.jidolstar.vo.Class1으로 직렬화해준다. Flex에서는 com.jidolstar.vo.Class1이 만들어진 Class1.as파일의 Class명 위에 [RemoteClass(alias="com.jidolstar.service.domain.Class1")] 를 정의함으로서 registerClassAlias()에서 한 것과 동일하게 처리할 수 있지만 ActionScript 3.0환경에서 개발하므로 [RemoteClass]는 사용하기에 적합하지 못하다.

이 프로그램을 Release모드로 컴파일하면 89kb였다. Flex로 개발시에 262kb이였던 것에 비해서 분명 크게 줄었다. 이는 Flex 비주얼 컴포넌트에 관련된 것이 컴파일에서 제외되었기 때문이다. 하지만 순수하게 NetConnection으로만 만들었던 것이 1kb였다는 것을 감안한다면 여전히 크기가 크다. 위 코드에서는 RemoteObject를 이용했는데 NetConnection을 이용해보면 어떨까? 다음 코드를 보자.

 

package {
	import com.jidolstar.vo.Class1;
	import com.jidolstar.vo.Class2;

	import flash.display.Sprite;
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	import flash.net.Responder;
	import flash.net.registerClassAlias;

	import mx.collections.ArrayCollection;
	import mx.messaging.messages.*;

	/**
	 * NetConnection이용
	 */
	public class AS3NetConnectionTest extends Sprite
	{
		public function AS3NetConnectionTest()
		{
			registerClassAlias("flex.messaging.messages.RemotingMessage",RemotingMessage);
			registerClassAlias("flex.messaging.messages.AcknowledgeMessage", AcknowledgeMessage);
			registerClassAlias("flex.messaging.messages.ErrorMessage",ErrorMessage);
			registerClassAlias("flex.messaging.io.ArrayCollection",ArrayCollection);

			registerClassAlias("com.jidolstar.service.domain.Class1", Class1  );
			registerClassAlias("com.jidolstar.service.domain.Class2", Class2  );

			//create a netconnection
			var nc:NetConnection = new NetConnection();
			nc.objectEncoding = ObjectEncoding.AMF3;
			nc.connect("http://192.168.0.17/amf");

			//create the arguments that will be sent to the method
			var methodArguments:Array = [ 1, "jidolstar" ];

			//create a remoting message
			var remotingMessage:RemotingMessage = new RemotingMessage();
			remotingMessage.operation = "getData";
			remotingMessage.body = methodArguments;

			//The values of these 2 values come from the services-config.xml which is located in the ColfFusion server
			remotingMessage.destination = "mydest";
			remotingMessage.headers = {DSEndpoint: "my-amf"};

			//create a responder
			var responder:Responder = new Responder(result, fault);

			//make the call
			nc.call(null, responder, remotingMessage);
		}

		private function result(e:AcknowledgeMessage):void
		{
			trace("RPC Ok");
			trace(e.body);
		}

		private function fault(e:ErrorMessage):void {
			 trace("RPC Fail");
			 trace(e.faultString);
		}
	}
}

 

위 프로그램은 RemoteObject를 사용할 때와 동일하게 동작한다. 하지만 더 저레벨로 만들었기 때문에 프로그램 용량은 44kb로 RemoteObject를 사용할때보다 1/2로 줄었다. Flex로 개발할때와 비교할때 거의 6배 차이가 난다. 그러나 여전히 순수하게 NetConnection만으로 만들었을 때와 용량차이가 나는데 그것은 메시지 관련 클래스와 ArrayCollection을 컴파일할때 그와 연관된 클래스들이 포함되어 컴파일 되기 때문이다. 그 중에서는 우리의 목적인 AMF3 직렬화와는 상관없는 resource bundle에 관련된 것도 포함되어 있다. 필요하다면 resource bundle부분을 제외한 라이브러리를 만들어 내는 것도 하나의 방법이 될 수 있겠다. 일단 6배 이상 프로그램 용량을 줄일 수 있다는 것에 만족하려고 한다. 만약 이 부분에 대해서 더욱 가볍게 만드신 분이 있다면 함께 공유했으면 한다.(제가 그것까지 하기에는 시간이 ^^;;)

 

위 프로그램은 단순한 예제이므로 필요할 때 클래스화 시켜서 만드는 것이 좋을 것이라 생각한다.

 

 

관련글

 

 

 

Tour de Flex가 AIR버전에 이어 웹버전도 나왔다.

 

3월 말에 나왔는데 이제 알았다.

 

웹버전은 아래 링크를 따라가면 되겠다.

 

http://www.adobe.com/devnet/flex/tourdeflex/web

 

참고로 AIR버전은 Tour de Flex component explorer에서 설치하면 되겠다.

 

매우 재미있고 유익한 프로그램이다.

 

 

웹서핑하다가 Flex로 만든 아주 간단한 그림그리기 툴을 발견했다.

 

이 그림그리기 툴은 그래픽 관련 API를 사용하고, 로컬에 저장하기 위해 Flash Player 10 FileReference API를 사용한다. 만약 Flash Player 9이하라면 이 프로그램은 정상동작하지 않는다. Flash Player 10 부터 로컬에 Flash Player에서 만들어진 리소스(바이너리 데이타, 텍스트 데이타 모두)를 사용자의 인터렉션이 있다는 조건하에 저장이나 읽기가 가능해졌다. 이 프로그램은 단순히 그림그리는 툴을 만들었다는 내용보다는 이것을 강조한 것이다. Flash Player 9 이하의 버전이라면 이것을 구현하기 위해 반드시 서버로 이미지를 저장한 다음에 그 이미지를 가져오는 단계가 필요하다.

 

100줄도 안되는 MXML코드로 구현한 것이니 내용을 참고한다면 도움이 될 것이라 생각한다.

 

(Flash Player 10 이상에서만 동작한다.)

 

 

이것을 만든 사람 블로그

http://www.jamesward.com/blog/2009/04/16/flex-paint-2/

 

소스코드

http://www.jamesward.com/demos/FlexPaint2/srcview/index.html

 

참고글

[Flash Player 10]FileReference의 변경된 보안정책과 새롭게 추가된 기능에 대한 나의 생각

 

 

 

ActionScript 3.0 환경에서는 클래스내에서 함수의 오버로딩(overloading)을 지원하지 않고 있다. 그래서 클래스 설계시 알게 모르게 어려움이 따르게 된다.  보통 함수는 method1, method2 등으로 이름을 붙여서 오버로딩이 아니더라도 해결이 가능하다. 하지만 생성자의 경우에는 클래스에 딱 하나의 함수만 가능하므로 오버로딩 기능이 절실해진다. 다행히도 완벽하지 않지만 오버로딩처럼 구축하는 것은 가능하다.

다음 예제를 보자.

 

package
{
    public class Foo
    {
        public function Foo( ...args )
        {
            if( args[ 0 ] is Date )
            {
                _init( args[ 0 ] );
            }
            else
            {
            	if( args.length == 3 )
            	{
		            if( args[ 0 ] is Number && args[ 1 ] is Number && args[ 2 ] is Number)
		            {
		                _init( new Date( args[ 0 ], args[ 1 ], args[ 2 ] ) );
		            }
		            else
		            {
            			throw new ArgumentError( "인자는 Number형이여야 합니다." );
		            }
            	}
            	else if( args.length == 6 )
            	{
            		try
            		{
			            if( args[ 0 ] is Number && args[ 1 ] is Number && args[ 2 ] is Number &&
			            	args[ 3 ] is Number && args[ 4 ] is Number && args[ 5 ] is Number)
	            		{
	            			_init( new Date( args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], args[ 4 ], args[ 5 ] ) );
	            		}
	            		else
	            		{
	            			throw new ArgumentError( "인자는 Number형이여야 합니다." );
	            		}
	            	}
	            	catch( e:Error )
	            	{
	            		throw new ArgumentError( e.message );
	            	}

            	}
            	else
            	{
            		throw new ArgumentError( "인자수가 3개 또는 6개 여야합니다." );
            	}
			}
        }

        private function _init( date : Date ) : void
        {
            //do something
            trace( date.toString() );
        }
    }
}

 

생성자에 ...args를 받는 것을 볼 수 있다. ActionScript 3.0은 ...args를 이용해 함수에 입력되는 인자의 수 및 형태에 구애받지 않고 인자를 넘겨줄 수 있다. args는 Array이므로 args.length 속성을 이용해 인자수를 받을 수 있고 각각의 인자를 args[0]과 같은 방법으로 접근이 가능하기 때문에 형태도 체크할 수 있다. 이것이 ActionScript 3.0에서 문법적으로 안되는 Overloading을 해결하는 열쇠가 된다. 컴파일시에 문법적 문제를 해결할 수 없지만 그래도 런타임시에는 throw를 이용해 Error를 던져줄 수 있게 된다. 위 코드는 아래처럼 사용한다.

 

var bar : Foo;
bar = new Foo( new Date() );
bar = new Foo( 2007, 7, 12 );
bar = new Foo( 2007, 7, 12, 12, 3, 4 );
//bar = new Foo( "2007", "07", "12" ); //String으로 입력하여 Error발생
//bar = new Foo( 2007, 7 ); //인자수가 맞지 않아 Error발생

 

C++이나 Java등에서는 당연히 객체지향프로그래밍에서 중요하게 언급되는 Overloading이 ActionScript 3.0에서는 안되니깐 정말 답답하기 그지 없었지만 다행히 이 방법으로 해결해본다. 완벽하지 않지만 분명 오도가도 못할때 도움이 될 수 있는 팁이다. ^^;

 

 

제가 Adobe RIA 기술을 접한지 벌써 2년 반이 넘었습니다. 이전에 Flash라는 것이 단순히 멋진 인터렉션을 보여주기 위한 툴로만 여겼는데 Flex와 AIR기술을 접하면서 굉장히 가능성이 있는 기술이구나라고 강하게 들었었죠.  지금도 그 생각은 여전합니다.

 

그러나 국내에서 인터넷을 접속하는 99%이상 컴퓨터에 Flash Player가 설치가 되어 있음에도 불구하고 한글입력이 되지 않는 문제, 또는 한글입력이 느린 문제가  여전히 남아 있습니다. Adobe RIA 기술을 사용하는데 발목을 잡는 매우 큰 문제가 아닐 수 없습니다.

 

저는 개인적으로 한국 Adobe에서 이런 문제를 직시하고 개발자 선이 아닌 Adobe 정책적 문제로 언급이 되어서 처리해줬으면 좋겠지만 이상하게 한국 Adobe는 요지부동이라는 느낌이 강하게 드는군요. 한국어 레퍼런스 문제도 그렇고 방금 언급했던 한글문제도 마찬가지고요.

 

이러한 문제를 해결하고자 모인 팀이 바로 "한글문제 공동대응팀"입니다.


Adobe RIA 기술의 한글 문제를 개발자들의 힘으로 극복하고자 힘써보자는 취지가 강합니다. 물론 문제 해결을 위한 개발을 직접할 수 없지만 우리의 뜻은 충분히 전달할 수는 있습니다. Adobe RIA 기술을 한글 문제점을 드러낸다고 해서 이런 문제가 있기 때문에 사용하기 곤란하겠네라는 생각보다는 이런 문제를 해결하려 하고 있구나라는 긍정적인 모습으로 바라보셨으면 좋겠습니다.

 

2009년 4월 11일(토요일)에 숭실대학교에서 "한글문제 공동대응팀 해오름 모임식"을 가집니다.
아래는 행사개요 및 행사진행내용입니다.

 

행사 개요
 
날짜 : 2009년 4월 11일(토)
장소 : 숭실대학교 벤처관 3층 대강의실
시간 : 오후 2시~오후 6시
주최 : Flash Platform 한글문제 공동대응팀
주관 : 숭실대학교 글로벌미디어학부
후원 : 숭실대학교

 

아젠다

     14:00 ~ 14:10 : 학교 시설 사용과 관련된 안내 (이희덕)
     14:10 ~ 14:30 : 한글 대응팀 소개 및 기조연설 (이희덕)
     14:30 ~ 15:00 : Flash Platform 게임개발 노하우 (이정웅)
     15:00 ~ 15:30 : FlarToolKit으로 구현하는 증강 현실 (옥상훈)
     15:30 ~ 16:00 : Flash Platform 한글문제 (이희덕)
     16:00 ~ 16:40 : 토론의 장 (대응팀 전원)
     16:40 ~ 17:20 : 잡부 Flex 개발자를 위한 Flex 스킨 (김학영)
     17:20 ~ 17:50 : Creating Visual Experiences with Flex (이준하)
     17:50 ~ 18:00 : 정리 및 폐회

 

자세한 내용은 온오프믹스를 통해 참고하시고 참석신청을 해주시면 되겠습니다.

Flash Player가 Cross OS, Cross Browser가 된다고 한다지만 유독 한글 입력 문제에 있어서 만큼은 제대로 되지 않는 경우가 있다. 가령 MS Windows 환경에서 Explorer외에 Firefox, Safari, Chrome, Opera 등에서 SWF를 브라우저에 Embed할 때 wmode를 transparent로 지정하면 TextField에 한글입력이 되지 않는다.(2009년 3월 25일 현재)

 

본인은 스타플(http://starpl.com)의 별지도에 댓글 달기 기능을 추가하면서 사용자의 OS, Browser를 판별하여 한글입력이 되지 않는 Browser의 경우 "*한글 입력이 되지 않아요!"라는 문구를 넣어야 했다.

 

 

위처럼 IE 경우엔 한글입력이 잘되므로 문구가 필요없다.

 

 

그러나 FireFox의 경우에는 한글입력이 되지 않아서 문구가 들어간다. 저 문구를 누르면 문제에 대한 안내페이지로 이동하게 되어 있다.

 

불행히도 ActionScript 3.0 라이브러리에서는 사용자가 어떤 브라우져를 이용하는지 알 수 있는 API를 제공하지 않고 있다. 그럴만도 한 것이 SWF는 브라우저에 Embed되어 작동되는 것 외에도 Flash Player 자체에서도 동작하기 때문일 것이다. (참고로 flash.system.Capabilities의 os 속성을 이용해 운영체제의 상세정보는 얻어올 수 있다.)

 

다행히도 사용자의 Browser정보를 알 수 있는 방법은 있다. 그것은 JavaScript 를 이용하는 것이다. JavaScript를 이용해만 쉽게 Browser 종류, 버전, OS 종류등을 얻어올 수 있다.

 

관련정보를 검색해본 끝에 Browser 종류, 버전, OS 종류를 범용적으로 사용할 수 있는 Javascript 코드를 발견했다. 아래 링크를 참고하자.

 

Browser detect : http://www.quirksmode.org/js/detect.html

 

사용자는 Javascript코드내에서 단지 아래처럼 사용하면 그만이다.

 

  • Browser name: BrowserDetect.browser
  • Browser version: BrowserDetect.version
  • OS name: BrowserDetect.OS

 

너무 편하다. 그럼 이것을 어떻게 ActionScript 3.0 에서 사용할 수 있다는 것인가? 결과적으로 flash.external.ExternalInterface를 이용하면 된다. ExternalInterface의 call()메소드를 이용해 JavaScript 코드를 호출하면 된다. 여기서 두가지 선택을 하게 된다.

 

  1. 위의 BrowserDetect를 js 파일로 만들어 HTML에 포함해서 ActionScript 3.0에서 ExternalInterface로 호출
  2. 위의 BrowserDetect를 커스터마이징한 JavaScript 코드를 ActionScript 3.0에 삽입해서 ExternalInterface로 호출

 

첫번째 방법은 ExternalInterface를 학습한 사람이라면 쉽게 접근할 수 있을 것이다. 하지만 AS3 코드와  JavaScript 코드가 둘로 나뉘어 관리하기가 힘들어지는 단점이 있다. 그래서 하나의 AS3에서 관리할 수 있는  두번째 방법으로 문제를 해결하고자 한다.

 

package com.starpl.utils
{
	import flash.external.ExternalInterface;

	/**
	 * Browser detect
	 * @author Yongho Ji
	 * @since 2009.03.24
	 * @see http://www.quirksmode.org/js/detect.html
	 */
	public class BrowserDetectUtil
	{
		/**
		 * Here we define javascript functions which will be inserted into the DOM
		 */
		private static const DATA_OS:String =
				"var dataOS = [" +
					"{" +
						"string: navigator.platform," +
						"subString: \"Win\"," +
						"identity: \"Windows\"" +
					"}," +
					"{" +
						"string: navigator.platform," +
						"subString: \"Mac\"," +
						"identity: \"Mac\"" +
					"}," +
					"{" +
						"string: navigator.userAgent," +
						"subString: \"iPhone\"," +
						"identity: \"iPhone/iPod\"" +
					"}," +
					"{" +
						"string: navigator.platform," +
						"subString: \"Linux\"," +
						"identity: \"Linux\"" +
					"}" +
				"];";

		private static const DATA_BROWSER:String =
				"var dataBrowser = [" +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"Chrome\"," +
							"identity: \"Chrome\"" +
						"}," +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"OmniWeb\"," +
							"versionSearch: \"OmniWeb/\"," +
							"identity: \"OmniWeb\"" +
						"}," +
						"{" +
							"string: navigator.vendor," +
							"subString: \"Apple\"," +
							"identity: \"Safari\"," +
							"versionSearch: \"Version\"" +
						"}," +
						"{" +
							"prop: window.opera," +
							"identity: \"Opera\"" +
						"}," +
						"{" +
							"string: navigator.vendor," +
							"subString: \"iCab\"," +
							"identity: \"iCab\"" +
						"}," +
						"{" +
							"string: navigator.vendor," +
							"subString: \"KDE\"," +
							"identity: \"Konqueror\"" +
						"}," +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"Firefox\"," +
							"identity: \"Firefox\"" +
						"}," +
						"{" +
							"string: navigator.vendor," +
							"subString: \"Camino\"," +
							"identity: \"Camino\"" +
						"}," +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"Netscape\"," +
							"identity: \"Netscape\"" +
						"}," +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"MSIE\"," +
							"identity: \"Explorer\"," +
							"versionSearch: \"MSIE\"" +
						"}," +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"Gecko\"," +
							"identity: \"Mozilla\"," +
							"versionSearch: \"rv\"" +
						"}," +
						"{" +
							"string: navigator.userAgent," +
							"subString: \"Mozilla\"," +
							"identity: \"Netscape\"," +
							"versionSearch: \"Mozilla\"" +
						"}" +
					"];";		

		private static const FUNCTION_SEARCH_BROWSER:String =
			"document.insertScript = function ()" +
			"{"	+
				"if (document.searchBrowser==null)" +
				"{" +
					"searchBrowser = function ()" +
					"{" +
						DATA_BROWSER +
						"var browser = null;" +
						"for (var i=0;i<dataBrowser.length;i++)	" +
						"{" +
							"var dataString = dataBrowser[i].string;" +
							"var dataProp = dataBrowser[i].prop;" +
							"if (dataString) " +
							"{" +
								"if (dataString.indexOf(dataBrowser[i].subString) != -1)" +
								"{" +
									"browser = dataBrowser[i].identity;" +
									"break;" +
								"}" +
							"}" +
							"else if (dataProp)" +
							"{" +
								"browser = dataBrowser[i].identity;" +
								"break;" +
							"}" +
						"}" +
						"if( browser == null )" +
						"{" +
							"browser = \"An unknown browser\";" +
						"}" +
						"return browser;" +
					"}" +
				"}" +
			"}";

		private static const FUNCTION_SEARCH_BROWSER_VERSION:String =
			"document.insertScript = function ()" +
			"{"	+
				"if (document.searchBrowserVersion==null)" +
				"{" +
					"searchBrowserVersion = function () " +
					"{" +
						DATA_BROWSER +
						"var browser;" +
						"for (var i=0;i<dataBrowser.length;i++)	" +
						"{" +
							"var browserString = dataBrowser[i].string;" +
							"var browserProp = dataBrowser[i].prop;" +
							"if (browserString) " +
							"{" +
								"if (browserString.indexOf(dataBrowser[i].subString) != -1)" +
								"{" +
									"browser = dataBrowser[i];" +
									"break;" +
								"}" +
							"}" +
							"else if (browserProp)" +
							"{" +
								"browser = dataBrowser[i];" +
								"break;" +
							"}" +
						"}" +
						"var versionSearchString = browser.versionSearch || browser.identity;" +
						"var index;" +
						"var version;" +
						"version = navigator.appVersion;" +
						"index = version.indexOf(versionSearchString);" +
						"if (index != -1) " +
						"{" +
							"return parseFloat(version.substring(index+versionSearchString.length+1));" +
						"}" +
						"version = navigator.userAgent;" +
						"index = version.indexOf(versionSearchString);" +
						"if (index != -1) " +
						"{" +
							"return parseFloat(version.substring(index+versionSearchString.length+1));" +
						"}" +
						"return \"an unknown version\";" +
					"}"+
				"}"+
			"}";	

		private static const FUNCTION_SEARCH_OS:String =
			"document.insertScript = function ()" +
			"{" +
				"if (document.searchOS==null)" +
				"{" +
					"searchOS = function ()" +
					"{" +
						DATA_OS +
						"var os = null;" +
						"for (var i=0;i<dataOS.length;i++)	" +
						"{" +
							"var dataString = dataOS[i].string;" +
							"var dataProp = dataOS[i].prop;" +
							"if (dataString) " +
							"{" +
								"if (dataString.indexOf(dataOS[i].subString) != -1)" +
								"{" +
									"os = dataOS[i].identity;" +
									"break;" +
								"}" +
							"}" +
							"else if (dataProp)" +
							"{" +
								"os = dataOS[i].identity;" +
								"break;" +
							"}" +
						"}" +
						"if( os == null )" +
						"{" +
							"os = \"An unknown OS\";" +
						"}" +
						"return os;" +
					"}"+
				"}"+
			"}";	

		private static var initFlag:Boolean = false;
		private static var _browserVersion:String = null;
		private static var _browser:String = null;
		private static var _os:String = null;

		private static function insertScript():void
		{
			if( initFlag ) return;
			if (! ExternalInterface.available)
			{
			    throw new Error("ExternalInterface is not available in this container. Internet Explorer ActiveX, Firefox, Mozilla 1.7.5 and greater, or other browsers that support NPRuntime are required.");
			}

			initFlag = true;
			ExternalInterface.call( FUNCTION_SEARCH_BROWSER );
			ExternalInterface.call( FUNCTION_SEARCH_BROWSER_VERSION );
			ExternalInterface.call( FUNCTION_SEARCH_OS );
		}

		/**
		 * browser name
		 */
		public static function get browser():String
		{
			if( _browser ) return _browser;
			insertScript();
			_browser = ExternalInterface.call( "searchBrowser" );
			return _browser;
		}

		/**
		 * browser version
		 */
		public static function get browserVersion():String
		{
			if( _browserVersion ) return _browserVersion;
			insertScript();
			_browserVersion = ExternalInterface.call( "searchBrowserVersion" );
			return _browserVersion;
		}

		/**
		 * OS name
		 */
		public static function get OS():String
		{
			if( _os ) return _os;
			insertScript();
			_os = ExternalInterface.call( "searchOS" );
			return _os;
		}

	}
}

 

 

사용법은 매우 간단하다.

 

  • Browser name: BrowserDetectUtil.browser
  • Browser version: BrowserDetectUtil.version
  • OS name: BrowserDetectUtil.OS

 

ExternalInterface를 활용하면 매우 무궁무진하게 확장이 가능해진다.

 

단, ExternalInterface를 사용할 수 있는 조건을 잘 고려해야한다. 첫번째로 ExternalInterface를 지원하는 Flash Player 버전을 확인한다. 둘째로 SWF를 Embed할때 allowScriptAccess속성과 allowNetworking 속성을 체크한다. 두번째의 경우 본인 블로그에 있는 “위젯도 마음대로 못다는 네이버 블로그” 글을 보면 되겠다.

 

참고글

 



FDT를 이용해 순수 ActionScript 3.0 코딩의 세계로 빠져보기 위해 설치했다. Flex 프레임워크가 너무 무거워 ActionScript 3.0 코드로 주로 작성하게 될터인데 Flex Builder는 사용하기에 너무 부족한 면이 있는 것 같다. 그렇다고 해본적도 없는 Flash를 할 수 있는 것도 아니고… 그래서 선택한 것이 FDT이다.

 

http://fdt.powerflasher.com/

 

위 사이트에 가면 FDT를 다운로드 받을 수있다. 일단 30일 체험판이 있으니 사용해보고 정말 쓸만하다면 구입도 고려해볼 예정이다.

 

설치하고 실행하자마자  JVM terminated 에러가 떳다

 

 

 

이런 경우에는 설치 디렉토리내에 있는 자신의 컴퓨터 사양에 맞게 FDT.ini를 약간 수정하면 된다.

 

-vmargs
-Xmx512m
-XX:MaxPermSize=128m

 

잘 실행 된다. ^^

 

외관이 완전 이클립스다. Flex Builder 처럼 Eclipse를 가지고 만들었으니 당연하겠다.

 

 

 

Preferences를 살펴봤다. FDT에도 Flex SDK를 그대로 사용하는 것을 확인할 수 있었다. 더불어 Flex Builder에서는 못하는 AS2 코딩도 가능해진다.

 

 

FDT의 Code Style에서 Formatter를 보니 Braces({,})사용하는 방식이 맘에 안든다. 내가 사용하는 방식으로 바꿔야겠다. 아래 그림에서 Braces 탭을 선택해서 수정하면 되겠다.

 

 

 

FDT의 Core Libraries를 보니깐 여기에서 라이브러리를 등록할 수 있다는 것을 확인할 수 있었다.

 

 

새로운 Flash 프로젝트를 만들어보겠다.

 

 

 

Flash Player 9이하 환경에서  ActionScript 3.0으로 개발할 것이므로 Flex_3_SDK_0_Pure를 선택하면 되겠다.

 

역시 순수 ActionScript 3.0으로 개발할때 필요한 playerglobal.swc만 라이브러리로 등록된다.

 

 

 

직접 해보니깐 Flex Builder 3.0에서 보지 못했던 맘에 드는 구석이 많은 것 같다. 뭐 이미 FDT의 장점에 대해서는 구구절절 말하기 귀찮긴 하다. 사실 이제 처음 사용한거라 ㅎㅎ

 

 

FDT는 유료의 압박이 있는데 무료인 FlashDevelop 프로그램에 대해서도 관심을 가져볼려고 한다. 물론 FDT보다는 못하겠지만 많은 개발자가 즐겨 사용하는 것으로 알고 있기 때문에 관심이 간다.

 

선택의 폭이 넓어 좋다. ^^

 

 

 

ActionScript 3.0의 ApplicationDomain은 로드한 SWF파일 내부에 정의된 Class를 참조하는데 유용하게 쓰인다.

 

ApplicationDomain은 현재 hasDefinition(name:String):Boolean 과 getDefinition(name:String):Object 등 2개의 public 메소드를 지원한다. 이 메소드를 이용하여 ApplicationDomain내 정의된 클래스 정의 유무를 알 수 있고 그에 대한 참조를 얻을 수 있다. 다음은 사용 예이다.

 

var myInstance:DisplayObject = new
(ApplicationDomain.currentDomain.getDefinition("com.flassari.MyClass"));

 

위 코드의 경우 현재 애플리케이션의 ApplicationDomain상에 정의된 com.flassari.MyClass 클래스의 정의를 읽어와 DisplayObject 객체를 만드는 것이다. 사용하기 매우 쉽다. 그러나 클래스의 이름을 알고 있다는 전재하에 쓰일 수 있는 방법이다. 그럼 ApplicationDomain 내에 정의된 이름을 알지 못하는 클래스들의 이름을 목록을 알고 싶을 때는 어떻게 해야할까? 불행히도 ApplicationDomain 클래스는 이것을 지원해주고 있지 않다. ApplicationDomain.getAllDefinitions():Array 와 같은 메소드가 지원된다면 좋을 텐데 말이다.

 

해결방법이 있다. 바로 여기에서 소개하는 SwfClassExplorer 클래스를 이용하면 된다. 사용방법은 매우 단순하다. 하나의 static 메소드인 getClasses(bytes:ByteArray):Array 를 이용하면 된다. 아래 코드는 간단한 사용예제이다.

public function init():void {
	// Load the swf
	var urlLoader:URLLoader = new URLLoader();
	urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
	urlLoader.addEventListener(Event.COMPLETE, onSwfLoaded);
	urlLoader.load(new URLRequest("pathToSwf"));
}

private function onSwfLoaded(e:Event):void {
	// Get the bytes from the swf
	var bytes:ByteArray = ByteArray(URLLoader(e.currentTarget).data);
	// And feed it to the SwfClassExplorer
	var traits:Array = SwfClassExplorer.getClasses(bytes);
	for each (var trait:Traits in traits) {
		trace(trait.toString()); // The full class name
	}
}

 

이는 매우 유용함을 알 수 있다. SWF를 Loader가 아닌 URLLoader로 로드했음을 볼 수 있다. 그리고 실제 DisplayObject 객체를 생성하기 위해 다음과 같이 코딩하면 되겠다.

 

private var _loader:Loader;

public function init():void {
	var urlLoader:URLLoader = new URLLoader();
	urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
	urlLoader.addEventListener(Event.COMPLETE, onSwfLoaded);
	urlLoader.load(new URLRequest("pathToSwf"));
}

private function onSwfLoaded(e:Event):void {
	var bytes:ByteArray = ByteArray(URLLoader(e.currentTarget).data);
	var traits:Array = SwfClassExplorer.getClasses(bytes);
	for each (var trait:Traits in traits) {
		trace(trait.toString()); // The full class name
	}

	selectedClassName = traits[0];
	_loader = new Loader();
	var loaderContext:LoaderContext = new LoaderContext();
	_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoaderInit);
	_loader.loadBytes( bytes, loaderContext );
}

private function onLoaderInit(event:Event){
	var obj:Object;
	try {
		obj = new (_loader.contentLoaderInfo.applicationDomain.getDefinition(selectedClassName ));
	} catch (e:Error) {
		trace( "Can't instantiate class:\n" + e.toString() );
	}
	if (obj != null && obj is DisplayObject) {
		addChild( DisplayObject(obj) );
	}
}

 

결국 Binay형태로 받아온 SWF를 Loader클래스의 loadBytes()메소드를 이용해 비동기적으로 로드처리한 뒤에 Loader.contentLoaderInfo.applicationDomain의 getDefinition() 메소드를 이용해 객체를 생성하는 방식이다.

 

아쉬운 점은 여기서 loadBytes()를 이용한 비동기적 처리가 아닌 동기적 처리를 통해 클래스 참조를 얻어낼 수 있다면 더욱 좋을 것 같다.  사실 이 부분을 찾다가 이 예제를 찾아내게 된건데 매우 유용해서 포스팅 한 것이다.

 

세부적인 예제 및 소스는 아래 출처를 참고한다.

 

출처

Swf Class Explorer for AS3

소스

위 출처에서 소스를 다운받을 수 없으면 아래 링크로 다운받는다.

 

 

ActionScript 2.0 기반으로 만들어진 swf는 AVM1이며 ActionScript 3.0기반에서 작성된 것 swf는 AVM2이다. AVM2와 AVM1 간에 스크립트 통신이 직접적으로 진행되지 않고 LocalConnection만으로만 가능하다.

 

여기서 소개하는 AVM2Loader는 AVM1, AVM2 기반의 SWF를 모두 AVM2의 형태로 로드시켜 줌으로써 해당 SWF간 스크립트 통신이 가능하게 하는데 목적이 있다. 구현방법은 flash.display.Loader 클래스를 확장해 로드된 컨텐츠가 AVM1인 경우는 AVM2로 변경해준다. 정확한 의미는 본인도 잘 모른다. 시간이 허락한다면 분석해보려고 한다.

 

소개하는 자료는 TroyWorks Blog에 나온 자료임을 밝혀둔다. 직접 테스트는 안해봤다. ^^ 아래코드는 AVM2Loader이다.

package
{
	import flash.display.Loader;
	import flash.events.*;
	import flash.net.*;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	/**
	 * Loads both of AVM1 and AVM2 swf as AVM2.
	 */
	public class AVM2Loader extends Loader
	{
		private var _urlLoader:URLLoader;
		private var _context:LoaderContext;

		/**
		 * loads both of AVM1 and AVM2 movie as AVM2 movie.
		 */
		override public function load(request:URLRequest,context:LoaderContext=null):void
		{
			_context=context;

			_urlLoader=new URLLoader ;
			_urlLoader.dataFormat=URLLoaderDataFormat.BINARY;
			_urlLoader.addEventListener(Event.COMPLETE,_binaryLoaded,false,0,true);
			_urlLoader.addEventListener(IOErrorEvent.IO_ERROR,_transferEvent,false,0,true);
			_urlLoader.addEventListener(ProgressEvent.PROGRESS,_transferEvent,false,0,true);
			_urlLoader.addEventListener(Event.OPEN,_transferEvent,false,0,true);
			_urlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS,_transferEvent,false,0,true);
			_urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,_transferEvent,false,0,true);
			_urlLoader.load(request);
		}

		private function _binaryLoaded(e:Event):void
		{
			loadBytes(ByteArray(_urlLoader.data),_context);
			_urlLoader=null;
		}

		private function _transferEvent(e:Event):void
		{
			dispatchEvent(e);
		}

		/**
		 * loads both of AVM1 and AVM2 movie as AVM2 movie.
		 */
		override public function loadBytes(bytes:ByteArray,context:LoaderContext=null):void
		{
			//uncompress if compressed
			bytes.endian=Endian.LITTLE_ENDIAN;
			trace("loadBytes" + bytes[0] );
			if (bytes[0] == 0x43) {
				//trace(" hackA");
				//many thanks for be-interactive.org
				var compressedBytes:ByteArray=new ByteArray() ;
				compressedBytes.writeBytes(bytes,8);
				compressedBytes.uncompress();

				bytes.length=8;
				bytes.position=8;
				bytes.writeBytes(compressedBytes);
				compressedBytes.length=0;

				//flag uncompressed
				bytes[0]=0x46;
			}
			hackBytes(bytes);
			super.loadBytes(bytes,context);
		}

		/**
		 * if bytes are AVM1 movie, hack it!
		 */
		private function hackBytes(bytes:ByteArray):void
		{
			//trace(" hackB");
			if (bytes[4] < 0x09)
			{
				trace("hack 9");
				bytes[4]=0x09;
			}

			//trace(" hackC");
			//dirty dirty
			var imax:int=Math.min(bytes.length,100);
			for (var i:int=23; i < imax; i++)
			{
				if (bytes[i - 2] == 0x44 && bytes[i - 1] == 0x11)
				{
					bytes[i]=bytes[i] | 0x08;
					return;
				}
			}
		}
	}
}
아래는 AVM2Loader 테스트 소스이다.
import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.display.SimpleButton;
import AVM2Loader;

var avm1:AVM1Movie;

function testAudio(e:Event = null):void
{
	trace("testAudio");

	//var request:URLRequest = new URLRequest("video.swf");
	var request:URLRequest = new URLRequest("Flash8Movie.swf");
	var ldr:Loader;
	if (true)
	{
		trace("start AVM2Loader");
		ldr = new AVM2Loader();
	}
	else
	{
		ldr = new Loader();
	}

	ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, _onLoaderComplete);
	ldr.load(request);
}

function _onLoaderComplete(event:Event):void
{
	trace(" _onLoaderComplete");
	var info:LoaderInfo = event.target as LoaderInfo;
	trace(" _onLoaderComplete " + info.content);

	if (info.content is AVM1Movie)
	{
		avm1 = AVM1Movie(info.content);
	}
	else
	{
		addChild(info.content);
		MovieClip(info.content).stop();
		var dO:DisplayObject = DisplayObject(info.content);
	}
}

stage.addEventListener(MouseEvent.CLICK, testAudio);
testAudio();
 
관련글

 

ShowSpace(http://showspace.co.kr)는 사진을 여러개 넣어서 블로그 및 게시판등에서 공유할 수 있도록 한 사진 플레이어 제공 서비스이다. 기술적으로 Adobe Flex로 구현되어 있다.

 

 

위는 ShowSpace를 이용해 사진첩을 하나 만들었다. 마우스 드래그로 사진을 볼 수 있고 확대/축소도 가능하다. 사용자 편의 성을 위해 아래 스크롤바로 사진을 스크롤링해볼 수 있다. 또한 전체화면 보기와 블로그/게시판등에 붙여넣을 수 있도록 HTML태그 복사도 가능하다. 아직 Beta라 여러가지 미흡한 점이 보이지만 몇가지만 더 개선한다면 사용자들에게 좋은 경험을 줄 수 있을 것이라 생각한다. 본인이 생각하는 ShowSpace가 좀더 개선했으면 하는 부분은 다음과 같다.

 

  1. 여러가지 스킨 적용 플레이어 스킨이 오직 하나라는 점이 아쉽다. 여러스킨으로 변경할 수 있도록 한다면 훨씬더 좋을 것 같다.
  2. 다양한 크기 적용 ShowSpace를 다양한 크기로 볼 수 있도록 한다면 다양한 형태로 쓸 수 있게 될 것이다. 가령, 블로그에 위젯으로도 쓸 수 있을 것이다. 지금은 하나의 크기로 제한되어 있는데 이렇게 되면 다양한 곳에 공유하기 어렵지 않을까?
  3. 스크롤바 라이브 드래깅 기능 향상 현재 스크롤바를 이용해 사진을 볼 수 있다. 하지만 마우스로 이동한 뒤에나 사진의 위치가 바뀐다. 뭔가 어색해보인다. 이를 라이브 드래깅을 할 수 있도록 만들면 훨씬 사용성이 향상될 것이라 생각한다.
  4. 사진보여주기 방식 선택의 편리성 향상 ShowSpace는 1줄보기, 3줄보기, 다섯줄보기, 입체한줄보기 등의 사진보기 방식을 지원하고 있다. 하지만 사진첩 설정이 따로 있어 관리하므로 약간 불편함이 느껴진다. 하나로 통합시키던가 사용자가 선택해서 다양한 형태로 보여줄 수 있게 만들면 더욱 좋을 듯하다.
  5. 외부링크시 새창뜨기가 잘 안되는 문제 ShowSpace의 사진을 클릭하면 원하는 링크로 갈 수 있다. 좋은 기능임에도 불구하고 Flash Player 내부에서 새창을 띄우려고 하기 때문에 IE에서는 노란색 바가 뜬다. 이것을 방지하기 위해 javascript의 window.open과 as3의 navigateToURL을 적절히 섞어서 사용하는 것이 좋겠다.
  6. 너무 적은 업로드 허용 용량 오늘날 대부분의 사진은 1Mb를 넘는다. 하지만 사진 업로드시 1Mb 용량제한으로 사진올리기가 불편하다. 업로드 용량은 10Mb정도로 하되 보여주는 사진을 Resize해주는 것이 더욱 좋을 듯 하다. 그래야 사용의 불편함을 없앨 수 있지 않을까? 또한 Flash Player 10부터는 사용자 인터렉션이 있다는 전재하에 로컬의 자원 가공이 가능하다. 즉, 10이상의 사용자는 클라이언트에서 이미지 사이즈 조정이 가능하다는 의미이다. 그러므로 10이상의 경우에는 클라이언트에서 사이즈 조정해주어 서버로 보내주고 그 이하 버전의 경우 기존방식대로 사용할 수 있게 끔하면 서버부하를 줄여줄 수 있게 될 것이다.
  7. Flex 대신 Flash(순수 ActionScript 3.0)으로 개발 Flex는 사실 너무 무겁니다. ShowSpace처럼 위젯형태로 블로그나 게시판등에 붙여야하는 애플리케이션이라면 Flash기반으로 만드는 것이 훨씬 좋은 퍼포먼스를 줄 수 있을 것이라 생각한다. 물론 업로드 툴은 Flex로 그냥 놔둬도 괜찮을 듯 싶다.
  8. 외부 사이트의 사진도 볼 수 있도록 확장 사진을 꼭 업로드 방식으로만 지원할 것이 아니라 사진 URL을 입력하면 사진을 읽어와 보여주는 방법도 고려해볼 만하다. 물론 클라이언트에서는 crossdomain 정책적인 보안적인 문제가 걸려있긴 하다. 하지만 클라이언트가 아닌 서버측에서 지원한다면 할 수 없는 것도 아니다.
  9. 이미지 에디터 기능 추가 이미지를 약간 가공해서 이쁘게 꾸민것을 보여주고 싶은 사용자가 있을 것이다. 이런 사용자들을 위해 이미지 에디터 기능을 추가해서 사진을 이쁘게 꾸밀 수 있도록 배려하면 더욱 좋을 듯 싶다.

앞으로 많은 사람들이 이용하는 서비스가 되길 희망한다. ^^ 오해하시는 분들이 계신데... 이거 제가 만든 것 아닙니다.

 

Adrian Parr’s Blog 에 올라온 ActionScript 3.0 Code 라이브러리 모음이다. 매우 유용해서 긁어왔다.

 

3D Engines

3D Game Engines

3D Animation Framework

3D Physics Engines

Animation Tweening Kits

2D Physics Engines

Security

Audio Libraries

Particle Systems

Data Visualization

Loading Kits

OOP Frameworks

Other APIs and libraries

 

출처 : http://www.adrianparr.com/?p=83

1. Application Domain 사용 예제

 

지난 호에서 우리는 Application domain에 대해서 알아보았다. 지금까지 설명한 내용을 확인하기 위해 몇가지 예제를 만들고자 한다.

 

 [그림 1] 메인과 모듈에서 정의되어 있는 Sprite 클래스

 

[그림 1]은 메인 프로그램과 모듈 프로그램에 각각 정의되어 있는 Sprite를 확장한 클래스를 보여주고 있다. 메인은 Yellow, Blue를 가지며 둥근 모양이다. 모듈은 Blue, Red, Green을 가지며 사각형 모양이다. 서로 클래스 명이 중복되는 것은 Blue이다.

 

메인과 모듈에서 모두 아래와 같은 형태로 4개의 Sprite를 확장한 클래스의 정의를 참조한다.

var ClassRef:Class =
ApplicationDomain.currentDomain.getDefinition("클래스명") as Class;

 

클래스 참조값을 이용해 인스턴스를 생성하여 메인과 모듈에서 그림을 그려준다. 모듈을 불러올 때 메인 프로그램의 Application domain의 설정에 따라 메인과 모듈에 정의된 Sprite 클래스들을 참조 권한이 달라지게 될 것이다.

 

아래 코드는 모듈 프로그램에서 정의된 BlueSprite 클래스의 정의다. draw() 함수를 호출하면 30×30의 파란색 사각형을 그려준다.

 

 

//BlueSprite.as – 모듈 프로그램에서 정의됨
package com.jidolstar.blue
{
        import flash.display.Sprite;

        public class BlueSprite extends Sprite
        {
               public function BlueSprite()
               {
                       super();
               }

               public function draw():void
               {
                       graphics.clear();
                       graphics.beginFill(0x0000ff, 1);
                       graphics.drawRect(0,0,30,30);
                       graphics.endFill();
               }
        }
}

 

아래 코드는 메인 프로그램에서 정의되는 BlueSprite이다. 모듈에서의 정의와 달리 draw()함수를 호출하면 파란색 원을 그려준다. 메인과 모듈에서 정의한 이름(BlueSprite)은 같지만 그 기능은 다르다는 것을 기억하자.

 

//BlueSprite.as – 메인 프로그램에서 정의됨
package com.jidolstar.blue
{
        import flash.display.Sprite;
        public class BlueSprite extends Sprite
        {
               public function BlueSprite()
               {
                       super();
               }
               public function draw():void
               {
                       this.graphics.clear();
                       this.graphics.beginFill(0x0000ff, 1);
                       this.graphics.drawCircle(15,15,15);
                       this.graphics.endFill();
               }
        }
}

 

이처럼 메인과 모듈에 Sprite 확장 클래스를 만들어둔다.

모듈 프로그램은 다음처럼 작성된다.

 

 

//TestModule.as
package {
	import com.jidolstar.blue.BlueSprite;
	import com.jidolstar.green.GreenSprite;
	import com.jidolstar.red.RedSprite;

	import flash.display.Sprite;
	import flash.system.ApplicationDomain;

	public class TestModule extends Sprite
	{
		private var blue:BlueSprite;
		private var red:RedSprite;
		private var green:GreenSprite;

		public function TestModule()
		{
			super();

			var BlueSpriteRef:Class;
			var RedSpriteRef:Class;
			var GreenSpriteRef:Class;
			var YellowSpriteRef:Class;
			var msg:String = "";
			var sprite:*;
			var x:int = 0;

			try
			{
				var ClassRef:Class = ApplicationDomain.currentDomain.getDefinition("클래스명") as Class;
				BlueSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.blue.BlueSprite") as Class;
				sprite = new BlueSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				addChild( sprite );
			}
			catch (e:Error)
			{
				msg+= "Module에서 com.jidolstar.blue.BlueSprite 로드 실패n"
			}

			try
			{
				RedSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.red.RedSprite") as Class;
				sprite = new RedSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				addChild( sprite );
			}
			catch (e:Error)
			{
				msg += "Module에서 com.jidolstar.red.RedSprite 로드 실패n"
			}                       

			try
			{
				GreenSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.green.GreenSprite") as Class;
				sprite = new GreenSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				addChild( sprite );

			}
			catch (e:Error)
			{
				msg += "Module에서 com.jidolstar.green.GreenSprite 로드 실패n"
			}       

			try
			{
				YellowSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.yellow.YellowSprite") as Class;
				sprite = new YellowSpriteRef;
				sprite.x = x;
				sprite.y = 10;
				x += 40;
				sprite.draw();
				this.addChild( sprite );
			}
			catch (e:Error)
			{
				msg += "Module에서 com.jidolstar.yellow.YellowSprite 로드 실패n"
			}

			trace(msg);
		}
        }
}

 

메인, 모듈에서 정의되는 BlueSprite, RedSprite, GreenSprite, YellowSprite 클래스 정의를 모듈의 current domain으로부터 얻어와 성공하면 그림을 그려주는 형태로 되어 있다. 특별히 모듈에서는 BlueSprite, RedSprite, GreenSprite가 정의되도록 아래와 같은 코드가 있다.

 

private var blue:BlueSprite;
private var red:RedSprite;
private var green:GreenSprite;

 

메인 프로그램은 모듈과 거의 동일한 모습으로 만들어진다. 다른 점은 모듈을 읽어올 수 있는 부분과 Application domain을 설정하는 부분이 추가가 된다. Application domain을 적용하는 상황이 총 세가지이므로 중복되는 코드를 줄이기 위해 Base 클래스를 만들고 이 Base 클래스를 확장해 3개의 상황에 맞게 메인 프로그램 A,B,C를 작성한다. 그럼 Base 프로그램을 보자.

 

//MainAppBase.as
package
{
import com.jidolstar.blue.BlueSprite;
import com.jidolstar.yellow.YellowSprite;

import flash.display.DisplayObject;
import flash.display.Loader;
import flash.display.Sprite;
import flash.events.Event;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.text.TextField;
import flash.text.TextFormat;

public class MainAppBase extends Sprite
{
	private var blue:BlueSprite;
	private var yellow:YellowSprite;
	private var loader:Loader;
	protected var applicationDomain:ApplicationDomain;

	public function MainAppBase()
	{
		super();

		loader = new Loader();
		var request:URLRequest = new URLRequest("TestModule.swf");
		var context:LoaderContext = new LoaderContext();
		context.applicationDomain = applicationDomain;
		loader.contentLoaderInfo.addEventListener( Event.COMPLETE, completeHander );
		loader.load(request,context);
	}

	private function completeHander(event:Event):void
	{
		var BlueSpriteRef:Class;
		var RedSpriteRef:Class;
		var GreenSpriteRef:Class;
		var YellowSpriteRef:Class;
		var msg:String = "";
		var sprite:*;
		var x:int = 0;
		var ModuleRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("TestModule") as Class;
		var module:DisplayObject = new ModuleRef();
		//var module:DisplayObject = event.target.content as DisplayObject
		module.x = 0;
		module.y = 50;
		this.addChild( module );

		loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, completeHander );
		loader.unload();
		loader = null;

		try
		{
			BlueSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.blue.BlueSprite") as Class;
			sprite = new BlueSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg+= "Main에서 com.jidolstar.blue.BlueSprite 로드 실패n"
		}

		try
		{
			RedSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.red.RedSprite") as Class;
			sprite = new RedSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg += "Main에서 com.jidolstar.red.RedSprite 로드 실패n"
		}                       

		try
		{
			GreenSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.green.GreenSprite") as Class;
			sprite = new GreenSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg += "Main에서 com.jidolstar.green.GreenSprite 로드 실패n"
		}       

		try
		{
			YellowSpriteRef = ApplicationDomain.currentDomain.getDefinition("com.jidolstar.yellow.YellowSprite") as Class;
			sprite = new YellowSpriteRef;
			sprite.x = x;
			sprite.y = 10;
			x += 40;
			sprite.draw();
			this.addChild( sprite );
		}
		catch (e:Error)
		{
			msg += "Module에서 com.jidolstar.yellow.YellowSprite 로드 실패n"
		}

		trace(msg);
	}
}
}

 

 

completeHandler() 함수를 보면 모듈 프로그램에서 정의했던 것과 별반 다를 바 없다. 단, 불러온 모듈을 화면에 표시하기 위해 아래와 같은 코드가 추가가 되어 있다.

 

var module:DisplayObject = event.target.content as DisplayObject
module.x = 0;
module.y = 50;
this.addChild( module );

 

 

생성자에서 Loader 클래스를 이용해 해당 모듈 SWF를 불러오도록 만들었다. Application domain을 protected로 값을 받아 설정할 수 있도록 만든 것을 확인하기 바란다. 이렇게 만든 의도는 이 Base 클래스를 확장한 메인 프로그램의 생성자에 application domain을 미리 설정하라는 것을 의미한다.

 

아래는 앞서 만든 Base 클래스를 이용해 application domain을 새롭게 정의해서 사용하는 예이다. 생성자에 applicationDomain을 어떻게 설정했는지 확인하면 되겠다.

 

//MainAppUsageA.as
package {
	import flash.system.ApplicationDomain;
	public class MainAppUsageA extends MainAppBase
	{
		public function MainAppUsageA()
		{
			applicationDomain = new ApplicationDomain();
			super();
		}
	}
}
//MainAppUsageB.as
package {
	import flash.system.ApplicationDomain;
	public class MainAppUsageA extends MainAppBase
	{
		public function MainAppUsageA()
		{
			applicationDomain = ApplicationDomain.currentDomain;
			super();
		}
	}
}
//MainAppUsageC.as
package {
	import flash.system.ApplicationDomain;
	public class MainAppUsageA extends MainAppBase
	{
		public function MainAppUsageA()
		{
			applicationDomain = new ApplicationDomain(ApplicationDomain.currentDomain);
			super();
		}
	}
}

 

위에서 제시한 코드는 두 개의 ActionScript3 프로젝트를 만들어서 아래와 같은 구성으로 만든다.

 

[그림 2] 메인과 모듈 프로젝트 구성

 

위처럼 모듈 SWF파일을 메인 쪽에 복사해야 모듈을 불러서 동작시킬 수 있다. 3개의 메인 프로그램을 단독으로 실행할 수 있도록 하기 위해, 프로젝트 폴더 명에서 마우스 오른쪽 버튼을 눌러 컨텍스트 메뉴의 Properties를 클릭해서 뜨는 창의 좌측에 ActionScript Applications에 default로 지정된 것 말고도 나머지 2개를 추가해준다.

 

이렇게 하면 기본적인 테스트 환경이 완료가 된다. 위의 MainAppUsageA.as를 선택하고 실행해보자.

 

[그림 3] 메인과 모듈이 서로 다른 application domain 일 때 실행 화면

 

이것은 new ApplicationDomain()를 이용해 모듈의 application domain을 설정했을 때 결과이다. 메인과 모듈간에 application domain이 다르기 때문에 상대방에서 정의한 클래스를 참조할 수 없다. 윗부분에 메인부분이다. 메인에서 정의한 BlueSprite와 YellowSprite에 대한 정의만 사용하여 그림을 그렸다. 아래 부분은 모듈로 모듈에서 정의한 BlueSprite, RedSprite, GreenSprite 를 이용해 그림이 그려졌다는 것을 알 수 있다.

MainAppUsageB.as를 선택하고 실행해보자.

 

 

[그림 4] 메인과 모듈이 서로 같은 application domain 일 때 실행 화면

 

이것은 ApplicationDomain.currentDomain을 이용해 모듈의 application domain을 설정했을 때 결과이다.

위쪽의 메인 프로그램에서 그려준 것을 보면 자신이 정의한 BlueSprite와 YellowSprite를 이용해 둥근 모양의 그림을 그렸다. 또한 같은 application domain이므로 모듈에서 정의한 RedSprite와 GreenSprite를 이용해 사각형 모양의 그림도 그렸다. 메인 프로그램과 모듈에는 BlueSprite가 중복되었다는 것을 [그림 2]에서 미리 언급했었다. 이 경우 메인에 이미 BlueSprite가 정의된 상태였기 때문에 모듈의 BlueSprite는 무시되었다는 것을 확인할 수 있다.

 

MainAppBase.as의 completeHandler() 함수를 잘 살펴보면 loader.unload()를 호출하고 나서 모듈의 SWF파일을 Unload했음에도 불구하고 모듈에서 정의한 RedSprite, GreenSprite를 그대로 사용하고 있다는 것을 볼 수 있다. 즉, 부모의 application domain에 등록된 자식의 Class 정의는 그 자식이 Unload되더라도 가비지 컬렉션 대상이 되지 않는다.

 

마지막으로 MainAppUsageC.as를 선택하고 실행해보자.

 

 

[그림 5] 메인과 모듈이 application domain이 부모-자식 관계일 때 실행 화면

 

이것은 new ApplicationDomain(ApplicationDomain.currentDomain)를 이용해 모듈의 application domain을 설정했을 때 결과이다. Application domain이 부모-자식관계가 되어 메인 프로그램에서는 모듈에서 정의한 클래스를 참고할 수 없으므로 RedSprite와 GreenSprite 클래스의 정의를 읽을 수 없다. 하지만 모듈은 자식 application domain 에 있기 때문에 부모의 BlueSprite와 YellowSprite를 사용한다. 중복정의된 BlueSprite는 이미 메인 프로그램에 정의되어 있던 BlueSprite로 대체되어진다.

 

 

2. 정리하며

 

Application domain에 대해서 자세하게 알아보았다. 모듈화 프로그래밍을 하기 위해서는 반드시 숙지해야 하는 부분이다. ActionScript 3.0로 모듈 프로그래밍을 할 때는 Loader 클래스를 이용해야하는데 상당히 불편한 점이 많다. 하지만 Flex에서는 Module 및 ModuleManager, ModuleLoader 등을 이용해 모듈 프로그래밍을 더욱 간편하게 할 수 있도록 도와주고 있다. 하지만 내부적으로는 Loader 클래스를 사용하고 있기 때문에 Application domain의 개념을 알고 있어야 이 클래스들을 적절히 잘 사용할 수 있다.

 

<참고내용>

 

이 글은 adobeflex.co.kr에 기술문서로 올린 글입니다.

1. Application domain에 대해

1.1 모듈화와 모듈화에 따른 application domain의 역할

 

프로젝트를 하다 보면 모듈화를 할 필요가 생긴다. 모듈화는 하나의 메인 응용 프로그램에 모든 기능이 있는 대신, 관련된 기능을 떼어내어 모듈을 만들어 필요한 시점에 동적으로 불러 사용할 수 있도록 만드는 것을 의미한다.

 

가령 Flex로 만든 블로그가 있다고 가정하면. 블로그에는 글쓰기, 리스트, 방명록, 관리, 메뉴 등이 존재할 것이다. 이러한 것이 하나의 SWF 파일에 모두 존재한다면 보여주지도 않을 방명록, 관리 부분까지 처음 프로그램 실행부터 전부 불러와야 하는 부담이 생긴다. 그래서 필요에 따라 동적으로 읽어서 사용할 수 있도록 글쓰기에 관련된 SWF, 리스트에 관련된 SWF 등으로 나눠서 만들어 놓으면 필요할 때 사용할 수 있으므로 더욱 효율적일 것이다.

 

이렇게 모듈화를 할 경우 블로그 메인 프로그램과 각 글쓰기 모듈 프로그램, 리스트 모듈 프로그램, 방명록 모듈 프로그램간에 통신을 하게 될 것이고, 때에 따라서는 메인 프로그램에서 정의한 자원을 모듈에 포함할 것 없이 재사용할 필요도 있을 것이다. 이렇게 하면 모듈에 메인 프로그램과 동일한 중복 코드를 가지고 있을 필요가 없어 용량이 줄어들 수 있기 때문이다. 그렇게 되면 자연스럽게 메인-모듈, 모듈-모듈간에 자원을 공유하는 일이 발생하게 된다.

 

이 문서는 모듈화를 하는 방법을 제시해준다기 보다는 모듈화를 위해 선행적으로 알아야 할 한가지를 언급하고자 한다. 그 중 하나가 바로 여기서 지금부터 다룰 application domain(응용 프로그램 영역)이다. application domain은 서로 다른 SWF 프로그램끼리 자원 공유를 위한 영역을 구분 지을 수 있도록 도와준다.

 

Namespace와의 비교

 

application domain에 대해서 이해하기 위해 Namespace와 비교해 보도록 하겠다.

 

Namespace는 이름, 용어 혹은 단어 항목들을 위한 문맥(context)을 제공하는 추상적인 컨테이너(container)라고 정의되어 있다. Namespace는 Java나 Flex, ActionScript3에서 사용하는 패키지(Package)와 거의 동일하다. Namespace는 프로그램 안에 같은 이름의 Class가 있을 때 구분해주는 역할을 한다. 가령 com.jidolstar.URLUtil과 flash.jidolstar.URLUtil 이 있다면 URLUtil로 이름은 같지만 Namespace는 다르므로 이 클래스는 이름만 같지 전혀 다른 클래스이다. 이렇듯 Namespace를 잘 활용하면 같은 이름의 클래스로 다른 동작을 하도록 만들 수 있고 또는 프로젝트 진행시 클래스 명이 충돌하는 경우도 방지할 수 있다.

 

그럼 application domain의 경우는 어떨까? Namespace는 단일 응용 프로그램에 같은 이름의 Class를 구분하기 위해 사용되는 반면 application domain은 여러 개의 응용 프로그램간에 같은 이름의 Class를 구분하기 위해 사용된다.

 

프로그램들이 하나의 application domain안에 포함되어 있다면 같은 이름을 가진 Class는 단 한 개만 존재가 가능하다. 가령 부모 프로그램의 Class 정의만 남고 자식 프로그램의 Class는 무시된다. 반면 프로그램들의 application domain이 서로 다르다면 같은 이름을 가진 Class라 할지라도 이들 Class는 서로 다른 application domain에 존재하는 것이므로 다른 것으로 해석된다.

 

 

1.2 Application domain 개념

 

아래는 application domain에 대해서 간단히 설명하고 있다.

application domain은 Loader의 load() 또는 loadBytes() 메쏘드를 이용해 ActionScript3.0으로 만들어진 SWF 파일을 불러들일 때만 사용된다.

application domain를 적용하기 위해 ApplicationDomain 클래스를 LoaderContext 클래스의 applicationDomain 속성에 대입하여 Loader의 load() 및 loadBytes() 메쏘드를 호출시 인자로 LoaderContext의 인스턴스를 넘겨주면 된다.

application domain은 동일한 보안 도메인(security domain)안에서 하나 이상의 SWF 파일 안에 제작된 ActionScript3.0 코드들을 목록화하여 관리한다. 다시 말하면 하나의 application domain안에서 여러 개의 SWF 파일에서 작성한 ActionScript3.0 코드를 관리할 수 있다. 또한 서로 다른 SWF 파일간에 다른 application domian을 안에 있다면 SWF 파일에 작성된 ActionScript3.0 코드는 중복되어지지 않는다. ActionScript3.0 코드는 Class 형태로 제작되며 결과적으로 application domain을 이용해 SWF 파일에 정의된 Class들이 서로 같은 영역에서 정의될 수 있고 또는 다른 영역에서 정의될 수도 있다. 또한 부모 SWF 파일에서 자식 SWF 파일에만 Class 정의 사용할 수 있도록 허용할 수 있다.

위처럼 application domain은 SWF 프로그램에 ActionScript 3.0으로 제작된 Class 정의를 원하는 영역(도메인)에 나누는 역할을 한다. 어도비 라이브 독(Adobe Livedocs)에 의하면 application domain에 대해서 아래 중요한 사실을 언급하고 있다.

 

  1. SWF 파일 안에 있는 모든 코드는 하나의 application domain 안에 존재되도록 정의된다. 메인 응용 프로그램(main application)의 application domain은 current domain이다. system domain current domain을 비롯하여 모든 application domain을 포함하기 때문에 모든 Flash Player 클래스를 가지고 있다.
  2. system domain을 제외한 모든 application domain은 그와 연결된 parent domain을 가진다. 메인 응용 프로그램의 application domain에 대한 parent domain은 system domain이다. 같은 application domain안에서 부모 프로그램과 자식 프로그램이 있다고 가정하자. 이때 부모 프로그램이 불러들인 자식 프로그램 안에 클래스들은 그 부모 안에 같은 이름의 클래스가 정의되어 있지 않을 때만 정의되고 사용할 수 있다. 또한 자식 프로그램의 클래스가 부모의 있는 클래스를 재정의(override) 할 수 없다.

 

같은 보안 도메인(scrutiny domain) 안에서 LoaderContext의 Application Domain 속성에 자신이 사용하고자 하는 Application Domain 속성을 아래 4가지 중 하나를 선택하면 되겠다.

 

  1. 부모의 application domain을 상속받은 application domain
    이것이 기본값이다.
    이 선택은 new ApplicationDomain(ApplicationDomain.currentDomain) 구문을 이용하는 것이다.
    이 방식은 자식의 application domain에서 부모에서 정의한 Class를 직접 사용할 수 있다. 가령 자식에서 ApplicationDomain.currentDomain.getDefinition(“부모에서 정의된 클래스명”)로 접근이 가능하다는 것을 의미한다. 반면 부모는 자신의 application domain에서 자식에서 정의된 Class에 접근이 불가능하다.
    이 방식의 장점은 자식에서 부모에 이미 정의된 Class와 이름이 같은 Class를 정의해도 오류가 나지 않는다. 자식은 해당 Class에 대한 부모의 정의를 상속받기만 하며, 부모와 충돌하는 클래스 정의는 무시된다.
  2. 부모 자신의 application domain
    ApplicationDomain.currentDomain 구문을 사용하는 것과 같다. 자식 SWF 파일을 불러오면 부모와 자식이 상대방에서 정의한 Class에 직접 접근이 가능하다. 서로 ApplicationDomain.currentDomain.getDefinition()을 이용해 접근이 가능하다. 단, 자식에서 부모와 같은 Class를 정의를 사용했다면 자식의 Class 정의는 무시된다.
  3. 시스템 application domain을 상속받은 application domain
    이는 new ApplicationDomain(null) 구문을 사용하는 것과 같다. 이렇게 하면 부모와 자식간에 정의된 Class는 서로 다른 application domain에 있기 때문에 자신의 application domain 안에서는 상대방에서 정의한 Class를 참고할 수 없게 된다. 부모와 자식간에 같은 이름의 Class가 있더라도 application domain이 다르므로 자식의 Class정의가 무시되는 경우는 없다.
  4. 임의의 application domain을 상속받은 application domain
    이 방법은 application domin 계층 구도가 복잡해질 수 있으므로 지양하는 방법이다. 자식 SWF을 자신의 security domain 안에 있는 임의의 application domain을 이용해 사용하는 것으로 가령, new ApplicationDomain(ApplicationDomain.currentDomain.parentDomain.parentDomain)을 사용하면 자식 SWF이 현재 application domain의 부모의 부모 domian을 상속받아 application domain을 만든다.

 

때때로 부모와 자식이 상대방의 application domain에 접근해야 하는 경우도 있다. 상대방이 application domain에 접근할 수 있는 경우 ApplicationDomain.currentDomain으로 상대방의 Class 정의를 가져올 수 있다. 또 다른 경우 부모가 자식 SWF를 로드가 완료했을 때, Loader 함수의 인스턴스가 loader라면 loader.contentLoaderInfo.applicationDomain.getDefinition()를 이용해서 자식의 application domian에 접근하여 자식에서 정의한 Class를 사용할 수 있다. 자식이 로드되는 방식을 알고 있는 경우 부모의 application domian에 접근할 수 있는데 기본적으로 ApplicationDomain.currentDomain.parentDomin을 이용하여 부모에서 정의한 Class에 접근이 가능하겠다.

 

 

2. ApplicationDomain Class 사용법에 따른 동작방식 해석

 

아래 <그림 1>은 단일 도메인(domain1.com)에 존재하는 다양한 SWF 프로그램(module1.swf, module3.swf, application2.swf)을 메인 SWF 프로그램(application1.swf)에서 Loader 클래스의 인스턴스를 통해 불러서 사용하는 모습을 묘사하고 있다. 이때 불러오는 SWF 프로그램들은 각자의 application domain에 등록되어 있다는 것을 알 수 있다.

 


<그림 1> Application domain과 SWF 프로그램 간의 관계

 

 

아래 예제는 [그림 1]의 Usage B의 경우와 같다.

 

package
{
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.*;
    import flash.net.URLRequest;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;

    public class ApplicationDomainExample extends Sprite
    {
        private var ldr:Loader;
        public function ApplicationDomainExample()
        {
            ldr = new Loader();
            var req:URLRequest = new URLRequest("Greeter.swf");
            var ldrContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
            ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
            ldr.load(req, ldrContext);    

        }

        private function completeHandler(event:Event):void
        {
            var ClassRef:Class = ApplicationDomain.currentDomain.getDefinition("Greeter") as Class;
            var myGreeter:* = new ClassRef();
            var message:String = myGreeter.welcome("Tommy");
            trace(message); // Hello, Tommy
        }
    }
}

 

위 예제를 간단하게 설명하면 메인 프로그램에서 모듈 SWF를 불러올 때 메인과 같은 application domain으로 설정한다.  모듈 SWF 로드를 완료하면 모듈 SWF 파일에 정의된 클래스(Greeter)를 이용해 인스턴스를 생성하고  그 클래스의 메서드에 접근하는 것을 확인할 수 있다.

 

이처럼 부모(메인)가 Loader 클래스를 이용해서 ActionScript 3.0으로 작성된 외부 SWF 파일을 자식으로 불러와 자식의 클래스와 메서드에 접근할 수 있다. Loader 클래스의 context 속성에 LoaderContext 객체를 넘겨주는데 LoaderContext에 바로 applicationDomain 매개변수를 넣을 수 있다. 위 예제에서는 메인 프로그램의 application domain인 current domain을 매개변수로 넘겨주었다.  자식의 SWF 파일에 부모와 동일한 application domain을 적용하겠다는 것을 의미한다. 자식을 부모의 application domain에 포함했기 때문에 부모는 자식에서 정의한 Greeter Class를 부모의 application domain 안에서 사용할 수 있게 된다.

 

위의 과정에 대해서 좀더 자세히 알아보자. <그림 1>에서 application domain 사용 예가 3가지 있다는 것을 알 수 있다. 아래 <그림 2>는 application domain 안에서 어떻게 Class가 공유되는지 살펴보기 위한 것이다.

 

<그림 2> application domain을 지정하는 3가지 방법

 

 

<그림 2>에서 클래스 명에 X표는 로드 후 정의에서 무시되는 클래스라는 것만 일단 기억하자. 지금부터 <그림 2>에서 표현하고 있는 application domain의 3가지 사용법에 대해서 언급하고 그에 따른 동작방식에 대해 이해해 보겠다.

 

 

2.1 ApplicationDomain 사용법A 새로운 application domain 사용

var loader:Loader = new Loader();
var request:URLRequest = new URLRequest("application2.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain = new ApplicationDomain();
loader.load(request,context);

 

위 코드는 메인 프로그램인 application1.swf 내에서 짜여진다. 이는 메인 SWF 프로그램과 전혀 다른 application domain 안에 존재하도록 만든다. 불러오는 컨텐츠의 이름이 module2.swf가 아닌 application2.swf로 표현한 이유는 서로 다른 application domain이기 때문에 독립적인 실행이 가능할 수 있다는 전제가 들어갈 수 있기 때문이다. application2.swf의 parent domain은 system domain이 된다.

 

<그림 3> 다른 application domain으로 외부 SWF를 불러오는 경우

 

<그림 3>에서 application1.swf와 application2.swf가 완전히 다른 application domain이기 때문에 서로의 각자 영역에서 정의된 com1::Class1과 com2::Class2는 상대방의 프로그램에 의해 영향을 받지 않는다. 같은 클래스 이름을 가지는 com1::Class의 경우 각자 다른 영역에 존재하므로 이름만 같은 다른 클래스로 이해할 수 있다.

 

var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

 

참고로 위에서 사용된 getDefinitionByName() 함수는 내부적으로 ApplicationDomain.currentDomain.getDefinition()을 호출하도록 만들어져 있다.

만약 application1.swf에서 위와 코드와 같이 접근하면 application2.swf를 불러온 상태에서도 application1.swf에 정의한 com1.Class1에 접근하게 된다. 반대로 application2.swf에서 같은 방식으로 접근할 때 application1.swf로 불려졌다 할지라도 서로 다른 application domain이므로 application2.swf의 com1.Class1에 접근하게 된다. 즉, 자기 자신의 application domain 영역에 정의된 Class를 사용하며 서로 간섭을 일으키지 않는다.

 

var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;

 

위의 접근 방식은 <그림 3>에서 application2.swf에서만 사용이 가능하다. com2.Class2는 application2.swf에서 정의되었고 application1.swf의 application domain이 application2.swf의 것과 다르기 때문에 application1.swf에서는 이 Class를 사용할 수 없다. 완전히 다른 application domain 영역이므로 unload시 바로 가비지 컬렉션 대상이 된다.

 

application1.swf에서 application2.swf의 com2.Class2에 접근하기 위해서는 application2.swf의 application domain의 참조를 알아야하는데 아래와 같이 참조가 가능하겠다.

var ClassRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("com2.Class2") as Class;

 

 

2.2 ApplicationDomain 사용법B 부모의 current domain을 사용

 

var loader:Loader = new Loader();
var request:URLRequest = new URLRequest("module1.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain;
loader.load(request,context);

 

위 코드에서 module1.swf는 부모와 같은 application domain을 사용하고 있다. 이것은 부모 프로그램의 자원을 공유하겠다는 의미와 동일하다.

 

<그림 4> 부모 프로그램과 동일한 application domain을 사용하는 경우

 

<그림 4>에서 볼 수 있듯이 동일한 application domain에 있기 때문에 자식인 module1.swf는 부모 application1.swf 내에 정의된 클래스를 사용할 수 있다. 만약 클래스가 부모 프로그램에 정의되어 있지 않는 경우 자식 프로그램에서 정의된 클래스가 부모 프로그램의 application domain에 등록되어 함께 공유하게 된다. 자식 프로그램과 부모 프로그램에 같은 이름의 Class가 존재한다면 자식 프로그램에서 정의한 Class는 무시된다.

 

var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

 

 

위와 같이 양쪽 프로그램에서 클래스 정의를 참조한다고 할 때 이미 application1.swf에 com1.Class1의 정의가 존재한 상태에서 module1.swf가 불려온 경우 module1.swf에 정의된 com1.Class1은 무시가 되어버린다. 결과적으로, 양쪽 프로그램에서 getDefinitionByName(“com1.Class1”)에 의해 얻어진 Class참조는 바로 application1.swf에 있는 com1.Class1이다.

 

var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;
var ClassRef:Class = loader.contentLoaderInfo.applicationDomain.getDefinition("com2.Class2") as Class;

 

위 경우 두가지 모두 똑같은 com2.Class2의 클래스 정의를 얻는 방법이다. 같은 application domain에 있으므로 가능한 것이다. 단, application1.swf가 module1.swf를 로드하기 전에는 이와 같이 사용할 수 없다. 왜냐하면 application1.swf의 application domain에는 com2.Class2가 정의되어 있지 않기 때문이다. 하지만 module1.swf을 로드한 후에는 같은 application domain에 com2.Class2가 등록이 된다. 그러므로 application1.swf에서도 module1.swf에서 정의한 com2.Class2 클래스 정의를 이용할 수 있다.

 

이러한 방법은 RSLs(Runtime Shared Libraries)에서 사용되며 로드된 SWF는 RSL로 처리된다. 이 말은 RSL과 동일하게 가비지 컬렉션 대상이 될 수 없다는 것을 의미한다. 왜냐하면 메인 프로그램인 application1.swf의 application domain에 로드된 module1.swf의 클래스 정의가 이미 부모의 application domain 내에 등록되었기 때문에 자식인 module1.swf의 참조를 지우고 언로드(unload)하더라도 한번 등록된 Class 정의는 지워지지 않는다.

 

RSL과 다른 점은 RSL은 프로그램 실행시 특별한 코딩이 필요 없이 자동으로 로드하는 반면 이 방법은 필요한 시점에 동적 로드가 가능하다는 것이다. 자동으로 로드한 다는 것은 Flex 프레임워크가 자체 지원하고 있다는 것을 의미한다.

 

 

2.3 ApplicationDomain 사용법C 부모의 current domain를 상속받은 새로운 application domain 사용

 

var loader:Loader = new Loader();
var request:URLRequest = new URLRequest("module3.swf");
var context:LoaderContext = new LoaderContext();
context.applicationDomain =
new ApplicationDomain(ApplicationDomain.currentDomain);
loader.load(request,context);

 

new ApplicationDomain()시 인자는 parentDomain이다. 즉, 새로운 application domain을 작성하되 부모의 current domain을 인자로 넘겨준다.

 

 

<그림 5> 부모의 application domain을 이용해 새로운 자식 application domain을 생성하는 경우

 

 

이렇게 되면 application domain 자체가 부모-자식관계가 된다. <그림 5>에서 보면 application domain이 걸쳐있는 것을 볼 수 있다.

 

var ClassRef:Class = getDefinitionByName("com1.Class1") as Class;

 

application1.swf가 module3.swf를 불러온 후 com1.Class1을 접근하면 자식 프로그램인 module3.swf에서 정의된 com1.Class1은 무시되어 버린다. 왜냐하면 application1.swf에 이미 com1.Class1이 정의되어 있고 module3.swf은 application1.swf의 같은 application domain을 참고하기 때문이다.

 

var ClassRef:Class = getDefinitionByName("com2.Class2") as Class;

 

재미있는 사실은 이 경우이다.

 

application1.swf가 module3.swf를 불러왔지만 application1.swf에서 정의되지 않는 com2.Class2를 module3.swf에서 정의한 com2.Class2를 application1.swf의 application domain 에서는 이 클래스 정의를 참조할 수 없다. com2.Class2 정의는 자식 프로그램인 module3.swf의 application domain에서만 참조가 가능하다.

 

이 관계를 쉽게 이해하기 위해 Class를 상속받는 관계와 비교해 생각하면 된다. 자식은 부모에 있는 모든 자원을 사용할 수 있으나 부모는 자식에서 새로 정의된 자원은 사용할 수 없다라는 사실을 기억하면 이러한 관계가 쉽게 이해될 수 있을 것이다.

 

이 방법은 부모에서 정의된 클래스를 최대한 활용하면서 자식의 독립된 클래스 영역을 확보하는데 사용된다. 자식에서 정의된 클래스가 부모의 application domain에 포함되지 않으므로 부모가 자식의 SWF에 대한 참조를 포함하지 않는 경우, 자식에서 정의한 클래스는 모두 가비지 컬렉션 대상이 된다.

 

 

2.4 Application domain의 사용법에 따른 동작방식 표

 

지금까지 application domain을 적용하는 방법과 그에 따른 결과가 어떻게 되는지 알아보았다. 관련된 내용을 <표 1>에 정리해 보았다. 참고하길 바란다.

 

[표 1] Application domain 사용법에 따른 동작방식

 

3. Application Domain의 다양한 응용

앞에서 application domain의 사용법 및 동작방식에 대해서 알 수 있었다. 많은 경우 이 3가지 방법을 섞어가며 사용하게 될 것이다. 아래 몇 가지 예제를 통해서 어떻게 자원이 공유되는지 이해해보자.

 

<그림 6> application domain 응용 예시 도면 1

 

<그림 6>을 보자. 부모 프로그램인 application1.swf의 application domain안에서 module1.swf가 먼저 로드되고 module2.swf가 나중에 로드된다.

 

처음 로드될 때를 살펴보자. 앞에서 이미 언급한 것과 동일하게 module1.swf의 com1.Class1는 application1.swf에 이미 정의되어 있기 때문에 무시된다. 하지만 com2:Class2는 정의되어 있지 않았기 때문에 application domain에 등록되고 공유된다.

 

나중 로드될 때를 보자. module2.swf 에는 application1.swf에서 정의된 com1.Class1이 있으므로 무시된다. 또한 이미 module1.swf에 의해 application domain에 com2.Class2 등록되어 있으므로 module2.swf의 com2.Class2는 무시되어 버린다.


 

<그림 7> application domain 응용 예시 도면 2

 

<그림 7>의 경우 앞에서 언급한 것과 같은데 다른 점은 module이 그의 application domain안에서 다른 module을 불러들이고 있다.

 

메인 프로그램인 application1.swf에 이미 com1.Class1이 정의되어 있으므로 모듈에서 정의된 모든 com1.Class1은 무시된다. 두 모듈은 전부 application1.swf의 com1.Class1의 정의를 사용할 수 있다.

모두 로드가 완료된 경우에 application1.swf는 module4.swf의 com3.Class3를 사용할 수 없지만 module3.swf는 사용할 수 있다. 왜냐하면 application1.swf과 module4.swf의 application domain관계는 부모-자식 관계이기 때문이다.

 

<그림 8> application domain 응용 예시 도면 3

 

<그림 8>에서 Application domain은 앞에서 언급한 것과 동일하게 적용된다.

 

module5.swf에 다른 application domain을 가진 application2.swf가 로드되는 경우 메인 프로그램(application1.swf)과 module5.swf와 전혀 다른 영역을 가지므로 application2.swf안에 정의된 모든 클래스는 application1.swf와 module5.swf에서 정의된 클래스들과 별개가 된다.


이 글은 adobeflex.co.kr의 기술문서로 올린 글이다.

 

 

 

 

국내의 한 회사에서 Flex/Sliverlight관련 RIA 플랫폼 기반의 프레임워크를 오픈소스화 했다. 이름하야 OpenZET. Open 소스도 모자라 무려 한국어를 포함한 5개 국어(영어,프랑스어,스페인어,일본어)로 번역되었다. 회사자체에서 이렇게 오픈 소스를 한다는 것은 폐쇄적이인 국내 IT 시장에서는 매우 획기적이고 즐거운 소식이 아닐 수 없다. 더군다나 다국어 지원까지…

 

아주 많은 자료가 있는 것은 아니지만 현업에서 컴포넌트를 어떻게 만들고 응용하는가 알 수 있고 함께 정보를 공유할 수 있다는 점에서 매우 가치있게 평가한다.

 

다른 많은 RIA관련 회사들도 이렇게 오픈 마인드를 가지고 국내 RIA 시장에 활기를 넣었으면 하는 바람을 가진다.

 

공식홈페이지 : http://openzet.org/

 

Flex SDK 3.3 이 공식배포되었군요.

여전히 한글 문제는 해결된바 없습니다.

+ Recent posts