Flash에서 이미지를 표현하기 위한 방법은 VectorBitmap이 있다.

 

Vector은 점,선,면등의 단순한 수학적 정보만 이용하여 이미지를 표현한다. 그렇게 때문에 적은 용량으로 이미지를 표현할 수 있으며 특히 확대/축소에도 깨지지 않는 이미지를 표현하는데 탁월하다. 또한 곡선 및 각종 이펙트 표현력도 훌륭하다. 적은 용량으로 이미지를 표현할 수 있기 때문에 메모리 관리에 도움이 된다. 하지만 복잡한 이미지의 경우 도리어 퍼포먼스의 저하가 일어날 수 있으며 이 경우 이동과 같은 에니메이션을 줄 경우 CPU연산이 많아지는 단점이 있다.

 

Bitmap은 이미지를 표현하기 위해 Pixel정보를 이용한다. 하나의 Pixel정보에는 ARGB값을 가진다. Pixel 하나하나 정보를 가지고 다루므로 복잡한 이미지를 나타내는데 좋으며 Vector와 달리 이동 애니메이션을 주더라도 CPU연산이 적다. 하지만 이미지가 클수록 Pixel 정보량이 제곱단위로 커지기 때문에 확대/축소시에 메모리가 과다하게 사용될 여지가 있다. 잘못사용하면 너무 큰 이미지로 인해 Flash Player가 다운될 수도 있다.

 

아래는 필자가 다루고자 하는 Vector와 Bitmap 이미지의 장단점을 언급한다.(아래 장단점은 완벽한 것은 아니다. 가령 Vector라고 해도 객체수가 적으면 속도에 무리가 없기 때문이다. 적어도 필자가 하고 있는 프로젝트 내에서 이런 문제가 있다는 것을 미리 언급한다.)

 

  • Vector 이미지
    - 메모리 효율이 좋다.(확대/축소시에도 메모리 점유율 변화 없음)
    - 이동 애니메이션 처리가 느리다.(CPU 연산이 커짐)
  • Bitmap 이미지
    - 메모리 효율이 나쁘다.(확대시 메모리 점유율 커짐)
    - 이동 애니메이션 처리가 빠르다.(CPU 연산 적음)

 

Vector 이미지와 Bitmap 이미지를 연구하게된 배경은 필자가 스타플 서비스(http://starpl.com/)의 별지도를 만드는데 이 부분에 대한 정보가 필요했기 때문이다. 먼저 아래 글을 읽어보면 이해가 쉽겠다.

 

[Flash,ActionScript 3.0]DisplayObject cacheAsBitmap 속성의 적용시점 문제

[Flex/AIR] 메모리의 무서운 적! DisplayObject의 cacheAsBitmap 속성

 

스타플의 별지도

 

구글맵과 같은 일반 지도의 경우 타일(Tile)들의 구성요소는 Bitmap 이미지이다. 최적화된 Bitmap이미지이기 때문에 렌더링 시간도 짧고 이동도 빠르다. 필자가 작업한 별지도는 천체 데이터 및 사용자 별 데이터를 읽어와 Vector 이미지로 렌더링하는 방식이다. 따로 데이터 가공 및 렌더링 시간이 필요하고 Vector이미지는 일단 이동 애니메이션이 느리다. 별이 반짝거리므로 단순히 Bitmap으로 바꿔서 처리할 수 있는 것도 아니다. 결국 스타플 별지도는 기본적으로 Vector 이미지로 타일을 구성해야한다.

 

앞서 설명했듯이 Vector 이미지를 이용하면 메모리 문제는 발생하지 않는다. 문제는 이동시 속도가 느리다는 것이다. 마우스로 지도를 드래그해서 이동하는 경우 저사양 컴퓨터에서는 버벅거림이 발생한다. Vector 이미지 타일로 구성된 지도의 단점이다. 그래서 이것을 극복하기 위해 첫번째로 사용하려했던 기능이 flash.display.DisplayObject의 cacheAsBitmap 속성이였다. 이 속성을 true로 설정하면 타일이 Bitmap으로 캐싱되고 실제로 적용후 이동처리해보면 훨씬 부드러운 이동 애니메이션을 볼 수 있다.

 

하지만 cacheAsBitmap은 Bitmap 이미지를 만드는 것이므로 확대/축소에서 문제가 있다. 이 속성을 true로 설정하고 확대하면 메모리가 기하급수적으로 늘어난다. 그래서 간단하게 확대/축소전에 cacheAsBitmap을 false로 지정하면 되겠다고 생각했다. 하지만 이 속성이 false로 설정된 순간 캐싱된 Bitmap을 해제시켜주지 못했다. 결국 false로 지정하고 확대해도 여전히 메모리가 증가한다.

 

이를 극복하기 위해 BitmapData를 이용했다. 만들어진 타일(Tile) 클래스에 bitmapMode라는 속성을 정의하고 true이면 Bitmap이미지를 false이면 Vector이미지를 보일 수 있도록 만든다. 그렇게 해서 이동이 발생시에는 Bitmap이미지를 사용하고 평상시 또는 확대/축소시에는 Vector 이미지를 사용할 수 있도록 한다. 아래는 이것을 구현하는 간단한 코드이다.(완벽한 코드가 아니다. 단지 예시일 뿐이다.)

 

this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true );

public function bitmapMode( value:Boolean ):void
{
	if( _bitmapMode == value ) return;
	_bitmapMode = value;
	
	.....

	if( _bitmapMode )
	{
		var bitmapData:BitmapData = new BitmapData( getWidth()+100, getHeight()+100, true, 0x00000000 );
		var matrix:Matrix = new Matrix();
		matrix.translate( 50, 50 );						
		bitmapData.draw( this, matrix, null, null, null, false );
		bitmapImage = new Bitmap( bitmapData );
		bitmapImage.x = -50;
		bitmapImage.y = -50;
		addChild( bitmapImage );
		removeChild( vectorImage );
	}
	else
	{
		addChild( vectorImage );
		removeChild( bitmapImage );
	}

	.....
}


public function disposeBitmap():void
{
	bitmapMode = false;
	if( bitmapImage )
	{
		bitmapImage.bitmapdata.dispose();
		bitmapImage = null;
	}
}

private function onRemovedFromStage( event:Event ):void
{
	this.removeEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false );
	disposeBitmap();
}

 

bitmapMode 속성을 구현할때 주의할 사항은 bitmapMode가 true일 때마다 BitmapData를 생성하는 일은 매우 비효율적이라는 것이다. 왜냐하면 이동할때마다 BitmapData를 만드는 것이기 때문이다. 그러므로 처음 이동이 발생할때만 BitmapData를 만들도록 하는게 중요하다. 또한 bitmapMode가 false라고 해서 기존에 만든 BitmapData를 삭제해서는 안된다. 삭제하면 언급한데로 다시 BitmapData를 만들어야하기 때문이다.

 

또 한가지 여기서 발생하는 가장 큰 문제는 적절한 메모리 관리이다. BitmapData를 잘 삭제해주어야 메모리 문제가 발생하지 않기 때문에다. 단순히 위에서 언급한 bitmapMode 변경으로는 BitmapData를 제대로 삭제할 수 없다. 그러므로 타일(Tile)클래스에 disposeBitmap() 함수를 만들어 bitmap.bitmapdata.dispose()를 호출하여 비트맵을 삭제하도록 하고 타일이 removeChild 될때 이 disposeBitmap()을 호출하여 BitmapData를 완벽하게 삭제하도록 해준다. removeChild가 되는 시점은 Event.REMOVED_FROM_STAGE 이벤트 처리를 하면 바로 알 수 있다.

 

삭제된다는 이야기는 메모리에서 바로 삭제된다는 것을 의미하지 않는다. Flash Player는 가비지 컬렉터가 작동한다. 즉 메모리에서 삭제될 대상을 모아두었다가 어느 시점에 한꺼번에 삭제한다. 이는 메모리 관리의 효율적인 방법중에 하나로서 개발자는 가비지 컬렉터 대상이 되도록 모든 참조를 없애주도록 해야한다. 가비지 컬렉터에 대해서는 아래 필자의 글을 참고한다.

 

[Flex / AIR / ActionScript3] Event 청취자와 메모리 관리

Flash Player 의 가비지 컬렉션(GC) 동작 방식에 대해

 

메모리가 제대로 반환이 되는지 확인하기 위해 Flex Builder(현 Flash Builder)의 프로파일링 기능을 이용하거나 System.totalMemory를 적극적으로 활용하여 메모리 상태를 점검하는것이 좋겠다.

 

소개한 대로 Bitmap과 Vector 이미지를 필요할 때마다 바꿔가면서 처리하는 것이 필자가 발견한 최상 방법이였다. 적용시 키포인트는 적절한 메모리 관리와 BitmapData를 자주 생성하지 않도록 하는 것이다.

 

 

 

DisplayObject 속성중에 cacheAsBitmap을 사용해 본 적이 있나요?

 

cacheAsBitmap은 시각화된 객체를 Bitmap으로 캐싱해주는 역할을 하는 속성입니다. 이것이 true가 되면 DisplayObject는 Bitmap처럼 되는 것이지요. Flash에서 Bitmap과 상반되는 것이 Vector입니다. 일반적으로 Flash에서 그래픽적인 요소는 거의 Vector로 처리되지요. Bitmap과 Vector에 대한 차이점과 장단점을 굳이 설명안하겠습니다.

 

단지 제가 궁금하고 해결하고 싶은 문제는 cacheAsBitmap속성이 적용된 것을 확대/축소할때 입니다.

이 속성을 true로 하고 이동처리(마우스 Drag등)을 하는 것과 안한것은 엄청난(?)속도 차이를 보입니다. 복잡한 Graphic이 들어가고 이동이 필요한 경우 cacheAsBitmap을 true로 설정하여 애플리케이션의 퍼포먼스를 높힐 수 있지요. 하지만 cachAsBitmap은 메모리와는 적입니다. 가령 확대/축소할때 가장 큰 문제가 되는데 Bitmap이 확대/축소할때 이미지는 그 크기만큼 메모리를 잡아먹습니다. 아래 제가 쓴 글을 보시면 이해될 겁니다.

 

[Flex/AIR] 메모리의 무서운 적! DisplayObject의 cacheAsBitmap 속성

 

저는 이동시에는 cacheAsBitmap을 true로 설정하고 확대/축소할때는 false로 설정해서 퍼포먼스 개선을 하려고 합니다. 하지만 cacheASBitmap을 설정하더라도 적용되는 시점이 문제입니다. 확대/축소하기 전에 false로 지정하고 바로 확대/축소를 하면 메모리를 잡아먹는다는 것이지요. 다음 Render시점에서 확대/축소를 해야하는 것인가 생각이 드는데 아직 뾰족한 묘안이 없습니다.

 

혹시 저와 같은 경험을 하셨다던가 아니면 해결책을 알고 계신분 있다면 댓글 부탁드리겠습니다.

 

 

Flash Builder 4와 더불어 Flash Catalyst Beta 버전이 배포되었습니다. 이것도 여전히 공식배포 버전은 아니며 테스트 버전이라고 볼 수 있지만 거의 완성단계가 아닌가 판단이 됩니다.

 

Flash Catalyst(이하 FC)는 개발자와 디자이너가 협업의 도구입니다. 지금까지 Flex Builder의 디자인 모드 및 CSS를 통해 어느정도 디자인이 가능했지만 디자이너가 접근하기에는 너무나도 불편한 구조였습니다. 또한 디자이너가 MXML, ActionScript 3.0 코드까지 알아야하는 경우도 발생해서 더욱 다루기 어렵고 개발자와의 협업이 어려웠던 것이 사실입니다.

 

FC는 디자이너가 프로그래밍 코드를 전혀 알것없이 PhotoShop 또는 Illustrator 와 같은 디자인 툴로 작업한 PDS나 AI파일을 카탈리스트로 불러와 레이아웃, State등을 잡고 각종 Effect도 잡을 수 있도록 만들 수 있습니다. 그 결과물(MXML코드)을 개발자에게 넘겨주면 개발자가 MXML에 비지니스 로직을 추가하는 방법으로 진행이 됩니다. 완벽한 디자인 분리가 될 것이라는 것은 해봐야 알겠지만 아무튼 이런 툴이 만들어진 것은 분명 반가운 일입니다.

 

FC는 기존 Flash를 사용해본 디자이너라면 쉽게 접근해서 사용할 수 있는 구조로 되어 있습니다. 그러므로 학습하는데도 큰 무리가 없을 것으로 보입니다.

 

FC로 작업한 결과물은 Flex뿐 아니라 AIR로도 개발이 가능해집니다. 이것 또한 큰 매력이라고 할 수 있습니다.

 

FC는 아래 링크에서 다운로드 받을 수 있으며 30일 Trial버전으로 테스트해볼 수 있습니다.(Adobe 가입/로그인을 해야합니다.)

http://www.adobe.com/cfusion/entitlement/index.cfm?e=labs_flashcatalyst

재미있는 것은 Serial Number를 함께 제공해줍니다.

 

FC에 대해서 더욱 자세히 알고 싶다면 아래 링크를 참고하세요. 동영상을 꼭 보시길…

http://labs.adobe.com/technologies/flashcatalyst/

 

아래 링크는 GotoAndLearn 사이트에서 제공하는 Flash Catalyst and Flex 4에 대한 간단한 강의입니다.

Flash Catalyst and Flex 4: Part 1

Flash Catalyst and Flex 4: Part 2

 

 

1. Flash Catalyst 설치하기

시리얼 번호를 입력하라는 창이 나옵니다. 배포 페이지에서 Serial Numbers를 함께 제공하므로 그것을 입력해서 사용합니다.

 

사용권 계약입니다. 당연히 동의해야 설치가 가능하겠죠?

 

옵션 설정인데… 뭐 선택할 것이 없네요. 그냥 설치합니다.

 

아래 화면처럼 설치 진행률을 보여줍니다.

 

 

2. Flash Catalyst 사용해보기

필자도 처음 해보는 것이라 자세한 기능 설명은 해드릴 수 없습니다. 엉성하게 나마 소개해 드리겠습니다.

 

설치가 완료되면 실행해보시길 바랍니다. BETA 1이 선명하게 표시되어 있군요.

 

아래는 실행화면입니다. 한글버전이 지원되는군요. 매우 좋습니다.

 

잘 보시면 Adobe Illustrator AI파일에서, Adobe Photoshop PSD 파일에서, 마지막으로 FXG 파일에서 작업을 시작할 수 있음을 알 수 있습니다. 아직 이런 작업물이 없으므로 그냥 새로운 프로젝트를 만들어보겠습니다.

 

아래와 같은 창이 나옵니다. 프로젝트 이름과 폭,높이,색상을 정합니다. 이름에 한글입력은 전혀 안됩니다. 프로젝트 명이니 당연한 것인지도..

 

프로젝트를 만들어봤습니다. 매우 단순해보입니다. 아래 그림의 상단에 Page1은 Flex의 State(상태)를 의미합니다.


즉, 화면전환을 위한 것이지요. 오른쪽에 간단한 도구와 Flex Component도 볼 수 있습니다.

 

맨 아래 타임라인을 클릭해보면 아래처럼 볼 수 있습니다. 이것은 State 전환을 위한 설정입니다. FC로 State 및 각종 Effect도 줄 수 있다는 것을 예상 할 수 있습니다.

 

버튼을 올려봤습니다. 버튼이 선택되어 있는 상태에서 버튼 설정을 쉽게 할 수 있도록 만들어져 있습니다. 필자는 개발자라 오히려 한글로 표현된 것이 어색하네요.

 

단추모양편집을 눌러보겠습니다.

 

아래처럼 버튼의 상태모양을 편집할 수 있습니다. 버튼의 State를 변경할 수 있도록 되어 있군요. 되돌아 가려면 검정색으로 보이는 바에 프로젝트명을 클릭하면 됩니다.(아래 그림에서는 jidolstar를 클릭합니다.)

 

응용프로그램 상호작용의 +버튼을 눌러보면 Flex 컴포넌트의 CreationComplete 이벤트가 발생했을때 처리를 설정할 수 있도록 되어 있습니다. 가령 상태 변환 및 동작시퀀스 제작등입니다.

 

그 아래 사용자 정의 상호 작용을 누르면 마우스 이벤트에 대한 다음 동작을 정의할 수 있습니다.

 

다른 주제로 넘어가서…

 

FC의 화면의 페이지 상태 밑에 중복상태, 비어있는 상태 버튼이 있는 것을 볼 수 있습니다. 차이점은 선택되어 있는 상태를 중복해서 상태를 만들것이냐 아니면 새로 상태를 만들것이냐 차이입니다. 확실한 것은 눌러보면 알 수 있다는 ^^

 

아래처럼 상태를 만들어 봤습니다. 타임라인 부분도 변경된 것을 확인할 수 있습니다.

 

각 상태 변화를 보기 위해  다른 컴포넌트를 올려보겠습니다.

아래는 2번째 상태입니다. 기존 상태를 중복한 겁니다. 확인(CheckBox)를 추가했습니다.

 

아래는 가로 스크롤 막대(HScrollBar)를 넣어봤습니다.

 

이제 이것을 저장해보겠습니다. 확장자가 FXP이군요. 기억해야겠네요.

 

 

3. Flash Builder 4에서 로드해보기

 

Flash Builder 4를 실행해보시고 방금 만들 FC파일을 로드합니다.

 

아래처럼 Flex 프로젝트가 만들어지고 MXML 코드가 들어가 있습니다. 상태 및 각 컴포넌트 배치, Transition 설정까지 모두 다 있습니다. 또한 components에는 만든 컴포넌트의 스킨도 포함되어 있는 것을 확인 할 수 있습니다. 개발자는 이제 FC에서 만들어진 결과물을 가지고 비지니스 로직만 구축하면 됩니다.

 

4. 정리하기

Flash Catalyst는 필자도 처음 사용해본 것입니다. 그러기 때문에 더 자세한 내용은 전달 할 수 없었습니다. 앞으로 필자도 이와 관련된 연구를 계속해서 함께 정보공유를 할 수 있는 분위기가 만들어졌으면 합니다. 기존 Flex 개발자는 Flash Builder 4 및 Flex 4 SDK를 공부해야할 것이고 디자이너는 Flash Caalyst를 익혀야 할 것입니다. Flash Builder와 Flash Caalyst는 Adobe RIA 개발/디자인 최초(?)의 협업도구 입니다. 첫번째 버전이라 아마도 부족한 점이 있을 것이지만 앞으로의 발전을 기대해봅니다.

Adobe Flash Builder 4 Beta가 배포되었습니다. 공식판은 아니고요. Flash Builder 4는 Flex Builder 3의 차기버전으로 Flex SDK 4(Flex Gumbo)를 기본 SDK로 설정되어 있습니다. Flex SDK 4로 만들어진 애플리케이션은 기본적으로 Flash Player 10 버전에서 동작이 가능합니다. 물론 기존 Flex SDK 3도 사용할 수 있습니다.

 

아래 링크에서 언제든지 다운로드 받을 수 있습니다.(회원가입하셔야 합니다.) 윈도우, 맥 버전이 있으니 자신의 운영체제에 맞게 다운로드 받으시면 됩니다.

 

http://www.adobe.com/cfusion/entitlement/index.cfm?e=labs%5Fflashbuilder4

 

 

다음 글을 참고하세요.

 

1. 설치하기

위 링크에서 다운로드 받아 쉽게 설치할 수 있습니다.

 

처음 설치화면입니다.

 

C:/Program Files/Adobe/Flash Builder Beta 경로에 기본 설치되는군요.

 

설치중인 화면입니다.

 

이름도 Gumbo입니다. ㅎㅎ 아직 Beta라는 것이겠죠.

 

실행해 보겠습니다.

 

30일 Trial버전으로 사용해봅니다.

 

첫 실행 화면입니다. FB 문자가 선명하네요. FB는 Flex Builder가 아니라 Flash Builder 입니다.

 

 

 

2. 사용해보기

 

메뉴 구성 변화

FXP 임포트도 되고 Test Case Class, Test Suit Class도 만들 수 있도록 되어 있네요. Flex Builder로 테스트 주도형 제작이 가능해지겠군요.

 

Data 메뉴가 상당히 직관적으로 바뀌었네요. 또한 더욱 많은 서비스도 지원하도록 만들어졌고요. 진정한 RIA를 구축하기 위한 서버측 기술 지원을 배려한 기능이군요.

 

 

이전에 없었던 Data/Services, ASDoc, Network Monitor 등이 지원되네요.

 

아래는 네트워크 모니터툴입니다. RPC 통신등을 할 때 서버와 통신이 어떻게 되는지 확인하는데 요긴하게 사용할 수 있을 것 같습니다. 이제 Http Watcher가 필요 없겠군요!!!!!

 

 

Flex 코딩해보기

Flex Project를 만들어보겠습니다. SDK를 Flex SDK 3.4와 Flex 4.0을 사용할 수 있도록 되어 있네요. Flex 4.0 기반에서 작업해 보겠습니다.

 

FDT(Flash Development Tools)에서 보이는 구조와 비슷하네요. 프로젝트에 사용하고 있는 Flex 4.0 SDK 라이브러리들도 보이고 각각의 (+)버튼을 누르면 정의된 클래스도 한눈에 볼 수 있습니다. 이거 정말 편해졌군요. SWC도 못봤던 netmone.swc, sparksins.swc등이 있습니다.

 

재미있는 것은 Flex Gumbo가 Fx이니셜로 컴포넌트가 만들어진 것으로 알고 있었는데 FxApplication이 아닌 그냥 Application입니다.

그리고 몇가지 namespace(fx, s, mx)로 구분 되었네요. ComboBox가 DropDownList로 이름이 바뀐 것 같습니다.

 

<?xml version="1.0" encoding="utf-8"?>
<s:Application
    xmlns:fx="http://ns.adobe.com/mxml/2009"
    xmlns:s="library://ns.adobe.com/flex/spark"
    xmlns:mx="library://ns.adobe.com/flex/halo"
    minWidth="1024" minHeight="768">
    <s:Panel x="14" y="44" width="250" height="200" title="타이틀입니다.">
        <s:Button x="15" y="12" label="버튼"/>
        <s:CheckBox x="15" y="46" label="체크박스"/>
        <mx:ColorPicker x="16" y="71"/>
        <s:DropDownList x="103" y="13"/>
        <s:RichEditableText x="92" y="50" width="147" height="39" text="RichEditableText 컴포넌트"/>
    </s:Panel>
</s:Application>

 

디자인 모드입니다. 스킨도 바뀌고 새로운 컴포넌트도 있네요.

 

실행해볼까요?

헛! 깔끔해진 실행화면!, Flex가 이젠 기본스킨도 이쁘게 나왔네요.

 

 

Theme 선택 기능

프로젝트의 Properties를 보면 Flex Theme가 추가되었습니다. 와우! 그런데 아직 기능이 완벽하지 못한 것 같습니다. 적용이 잘 안되네요 ㅡㅡ;

 

 

Flash CS4 Component 연동

디자인 모드에서 Components를 보시면 Custom에 New Flash Componet, New Flash Container가 있습니다. 즉 Flash CS4에서 만들어진 SWC와 연동이 가능해졌다는 것을 의미합니다.

 

드래그 해서 디자인 뷰에 붙이면 아래처럼 화면에 보이고 선택하면 오른쪽 Properties에 Create in Adobe Flash 버튼이 보입니다.

 

 

버튼을 누르면 Class를 선택할 수 있게 하고 SWC 파일을 선택합니다.

Create 버튼을 누르면 Flash CS4가 실행되면서 컴포넌트를 만들 수 있게 되어 있네요.

 

 

ASDoc 기능

와우! Panel에 마우스를 올려보니 간단한 설명도 나오네요. Eclipse에서는 되던건데… ㅎㅎ

 

앗… ASDoc View가 바로 이런 역할을 가지는 거였군요. 해당 컴포넌트를 선택하면 ASDoc View에 아래처럼 보입니다. 강력하군요.

 

 

Getter/Setter 생성기능 / ASDoc Commnet 기능

 

엄청난 기능(?)이 추가되었군요. Eclipse에서는 당연히 되던건데 왜 Flex Builder에서는 안되나 궁금했던 기능이 이제야 추가되었군요.

 

properties를 만들고 해당 속성의 Getter/Setter 기능을 아래처럼 추가할 수 있습니다. 단축키는 없네요. ㅡㅡ;

또한 Add ASDoc Commnent 기능도 추가 되었습니다. @author, @since 등을 자동으로 붙여줄 수 있으면 좋으련만… 아마 설정이 가능할겁니다. ^^

 

 

이벤트 핸들러 및 Service Call 등록 기능

 

코딩작업 없이 Event 핸들러 및 Service Call 기능을 추가할 수 있게 되었습니다.

 

 

위 그림에서 처럼 버튼을 선택하고 Properties에서 On click에 Generate Event Handler 를 선택하면 아래처럼 이벤트 핸들러가 생성됩니다. 넘 좋군요. ^^

 

버튼을 click하면 바로 아래 코드가 실행되는겁니다.

 

그리고 Generate Service Call을 누르면 Data/Services를 생성해서 연결할 수 있습니다. 와우 RIA 개발툴 다운 면모가 보이는군요.

 

 

File Templates 기능

메뉴의 Windows의 Perferences에 들어가면 아래와 같은 창이 뜹니다.

여기에서 Flash Builder > File Template 가 있습니다. 이를 선택하면 자신의 코딩방식을 설정할 수 있도록 되어 있습니다. 좋은 기능이네요.

 

 

3. 정리하며

이외에도 아직 제가 발견하지 못한 기능이 상당합니다.

 

이제 FDT를 사용하지 않아도 Flash Builder로 충분히 예전에 지원되지 않았던 기능들을 사용할 수 있게 되었습니다. 몇가지 버그가 있는 것 같지만 그 버그만 잘 잡아준다면 훌륭합니다.

 

제가 적지 않은 기능이 있다면 소개해주세요. 그리고 제 블로그에 트랙백(엮인글) 적극 환영합니다. ^^

오늘 한국 Adobe RIA 공식사이트에서 발생된 ACC 뉴스레터를 이메일로 받았습니다. 

 

ACC는 Adobe Community Champion의 약어로 글로벌 정책으로 Adobe 각 제품군(Flash/Flex/AIR등)별 전문가들을 말합니다.  ACC 뉴스레터는 1개월에 한번씩 정기적으로 회원들에게 이메일을 통해 전송되며 한국 Adobe RIA 공식사이트에 가입하시면 받으실 수 있습니다.

 

제가 쓴 글을 비롯해 ACC분들의 글들이 올라왔습니다. 

 

ACC는 단순히 전문가만을 의미하지 않습니다. 또한 ACC라서 최상의 기술을 가지고 있는 것 또한 아닙니다. ACC가 아니더라도 드러내지 않는 실력 가지신 분들도 많답니다. 그럼 ACC는 어떤 사람들일까요?

 

제가 생각하는 ACC는 바로 Adobe 기술의 전도사입니다.

 

처음 Adobe 기술을 접하시거나 Adobe의 새로운 기술을 알고 싶고 때로는 토론하고 싶다면 ACC 분들과 적극적으로 커뮤니케이션을 유지하시면 좋겠다는 생각을 합니다.

 

한가지 조언드릴 것은 Adobe 기술도 나름 분야가 방대합니다. Flash, Flash Lite, Flex, ColdFusion, AIR, LiveCycle, BlazeDS 등등.. 아무리 ACC더라도 자신이 잘알고 해본 분야는 제한되어 있고요. 다 아는 것이 아닙니다. 그러므로 각각의 ACC의 전문분야를 잘아시고 목적에 맞게 대화를 하시는 것이 좋을 것 같습니다.

 

저의 경우 스타플(http://starpl.com)에 Adobe Flex/Flash 기술을 도입하면서 얻은 지식을 공유할 수 있습니다. Java분야나 ColdFusion, Flash Lite는 저도 잘 모릅니다. 그러므로 스타플에서 사용된 별지도, 별꾸미기, 위젯, 이미지에디터 등 Adobe RIA 기술에 국한되어 답변할 수 있을 것 같습니다. 같은 ACC지만 저도 다른 ACC의 도움을 받습니다. 그러므로 이 글을 보시는 여러분도 ACC와 소통하실때 그 부분을 잘 참고하시길 바랍니다.

 

또 한가지!

 

소통의 도구로 가장 좋은 것은 블로그입니다. 글 쓰는게 어려우신 분은 어쩔 수 없다지만 제가 생각할 때 블로그만큼 좋은 소통의 도구가 없습니다. 제가 ACC가 된 것도 블로그를 통해 기술을 공유하고 또한 소통하면서 얻은 타이틀입니다. 제가 잘해서 또는 이쪽에 탁월한 전문가이기 때문은 아니라는겁니다. ACC는 기술의 전도사라는 말을 기억하시면 이를 쉽게 이해할 수 있을겁니다.

 

제가 블로그를 강조하는 이유는 가령 이렇습니다. 일전에 제가 [ActionScript 3.0] Stage.invalidate()를 호출해도 Event.RENDER 이벤트가 발생하지 않는 문제 해결 글을 올렸습니다. 이 글을 올린데에는 단순히 지식전달 수준이 아닙니다. 지식전달만 목적이라면 블로그 할 필요없죠. 댓글을 보시면 답이 나옵니다. 서로 지식이 공유되고 있지요. 이게 파격적인 겁니다. 잘 모르더라도 잘 정리해서 글을 적으면 함께 고민하는 사람과 이야기할 수 있고 서로 알아갈 수 있습니다. 블로그는 댓글 기능만 있는 것이 아니라 블로그의 글끼리 묶어주는 트랙백(엮인글)이 됩니다. 또한 RSS발행을 통해 관심분야 사람들 유입경로를 만들어줍니다. 아직도 개발자인데 블로그 안하시나요? 공부하고 싶고 인맥을 구축하고 싶은데 블로그를 안하시나요? 실력이 없어서 또는 잘 모르는 지식 때문에 괜히 악플달릴까 고민하는 것은 좋지 않는 생각입니다. 틀리는 것을 부끄럽다고 생각하지 말아야 발전이 있는 것이니깐요. 블로그 하시는 것이 여러분의 실력을 키우는데 큰 도움이 되는 것을 강조하고 싶습니다. 적어도 저는 블로그의 혜택을 톡톡히 보는 사람중에 한사람이라 자신있게 강조합니다. ^^

 

ACC는 앞으로도 Adobe RIA 기술의 전도사로서 여러 활동을 할겁니다. 기대해주시고 기억해주세요. ^^

Adobe Flex가 아닌 Flash CS3, CS4, ActionScript 3.0로 개발해본 사람이면 Stage를 자주 접하게 된다. 하나의 ActionScript 3.0 애플리케이션은 Stage 하나를 가지고 있다. 이것은 모든 DisplayObject 계열의 객체가 addChild()를 통해 시각화 과정이 완료후에 그 객체의 stage속성을 통해 접근이 가능하다. 모든 DisplayObject 계열의 객체에서 stage속성에 접근할 수 있다는 것은 stage가 매우 중요하다는 것을 암시하고 있다. 그러므로 잘 알고 활용해야 삽질을 방지할 수 있지 않을까?



1. Stage.invalidate()와 Event.RENDER 이벤트의 이해 


Stage 클래스에는 invalidate() 메소드가 있다. 이것을 호출하면 다음 렌더링 시점에 Event.RENDER 이벤트를 발생시킨다. 모든 DisplayObject 객체는 stage에 접근이 가능하므로 Event.RENDER 이벤트를 청취할 수 있다. 이는 애플리케이션 퍼포먼스를 향상시키고자 하는 개발자들이 반드시 알고 있어야할 사항이라고 생각한다. Stage의 invalidate()와 Event.RENDER 이벤트를 이용해 쓸데없는 렌더링을 예방함으로써 퍼포먼스를 향상시킬 수 있기 때문이다. 그 이유를 명확하게 알기 위해 다음 예제를 보자.


필자는 일반 ActionScript 3.0 프로젝트를 만들고 Sprite기반에서 애플리케이션을 만들고자 한다. 이때 두개의 클래스를 만드는데 하나는 Stage.invalidate()를 사용한 클래스이고 하나는 사용하지 않는것이다. 이 두개를 비교하면 Stage.invalidate()의 유용성을 확연히 알 수 있다.


InvalidateTest.as

package {
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;

	/**
	 * Stage.invalidate() 테스트 메인 
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class InvalidateTest extends Sprite
	{
		public function InvalidateTest()
		{
			super();
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			var button1:NotUseInvalidateButton = new NotUseInvalidateButton();
			button1.setWidth( 100 );
			button1.setHeight( 100 );
			button1.backColor = 0xff0000;
			button1.lineColor = 0x00ff00;
			button1.x = 20;
			button1.y = 20;
			addChild( button1 );
			
			var button2:UseInvalidateButton = new UseInvalidateButton();
			button2.setWidth( 100 );
			button2.setHeight( 100 );
			button2.backColor = 0xff00ff;
			button2.lineColor = 0x0000ff;
			button2.x = 130;
			button2.y = 20;
			addChild( button2 );			
		}

	}
}

위처럼 두개의 버튼 클래스를 만들었다. 2개 모두 폭, 높이, 배경색, 선색을 지정하도록 되어 있다. 결과는 비슷하다. 하지만 내부적으로 NotUseInvalidateButton은 내부에 Stage.invalidate()를 사용하지 않았고 UseInvalidateButton은 사용했다. 소스를 보자.


NotUseInvalidateButton.as

package
{
	import flash.display.Sprite;

	/**
	 * Stage.invalidate()를 사용하지 않는 Button
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class NotUseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		public function NotUseInvalidateButton()
		{
			super();
		}
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			drawNow();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			drawNow();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			drawNow();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			drawNow();
		}
		
		public function drawNow():void
		{
			trace( "[NotUseInvalidateButton] drawNow" );			
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}
		
	}
}


UseInvalidateButton.as

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

	/**
	 * Stage.invalidate()를 사용하는 Button
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class UseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		private var isDrawNow:Boolean = false;
		private var isInvalidated:Boolean = false;
				
		public function UseInvalidateButton()
		{
			super();
			
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}		
		
		private function onAddedToStage( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if( isInvalidated )
			{
				isInvalidated = false;
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.invalidate();
			}
		}
		
		private function onRender(event:Event):void
		{
			if( this.stage )
			{
				this.stage.removeEventListener( Event.RENDER, onRender, false );
			}
			if( isDrawNow )
			{
				isDrawNow = false;
				drawNow();
			}
		}
		
		private function invalidate():void
		{
			if( this.stage )
			{
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.invalidate();
			}
			else
			{
				isInvalidated = true;	
			}
		}
		
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function drawNow():void
		{
			trace( "[UseInvalidateButton] drawNow" );
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}		
		
	}
}

디버깅 모드로 프로그램을 실행해보면 콘솔창에 다음과 같이 출력된다.


[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[UseInvalidateButton] drawNow 


NotUseInvalidateButton은 drawNow()메소드가 4번 호출되고 UseInvalidateButton은 1번만 호출된다. 이것만 보더라도 쓸데없는 렌더링을 줄여준 UseInvalidateButton 클래스가 더 잘 설계되었다는 것을 확인할 수 있다. 


NotUseInvalidateButton를 잘 살펴보면 setWidth(), setHeight(), set backColor, set lineColor 를 호출할 때마다 drawNow() 메소드를 호출한다. 개발자라면 이 4가지 속성이 모두 적용된 다음에 drawNow()를 호출하여 한번만 그려주고 싶어질 것이다. 이때 유용하게 사용할 수 있는 것이 Stage.invalidate() 이다.


UseInvalidateButton을 보자 NotUseInvalidateButton보다 약간 복잡해보이지만 잘 따라가보면 어렵지 않게 분석이 가능할 것이다. 결국 4개의 속성이 설정되면 invalidate() 메소드가 호출되고 Event.RENDER 이벤트를 받는 onRender()에서 drawNow()가 호출되도록 한다. 이렇게 되면 4개든 여러개든 할 것 없이 렌더링에 영향을 주는 다중 속성을 설정할 때마다 drawNow()를 호출하여 그림을 그려주는 부담을 줄일 수 있다. 


그림을 그리는 행위는 일반 속성을 설정하는 것보다 더 비싼 비용을 가진다. 그러므로 그림을 그리는 것은 되도록 한번에 처리할 수 있도록 하는 것이 애플리케이션 퍼포먼스 향상의 중요한 요소가 될 수 있는 것이다. 



2. Stage.invalidate()의 버그와 해결방법 


하지만 이렇게 좋은 Stage.invalidate() 가 있음에도 불구하고 실제로 이 메소드를 호출해도Event.RENDER 이벤트가 발생하지 않아 당황스러운 경우가 필자에게 있었다. 그래서 어떤 것은 drawNow()가 호출되고 또 어떤 것은 호출안되는 상황이 발생한 것이다. 너무도 어이가 없지만 Flash Player 10으로 넘어오면서도 이 버그는 아직까지도 Fix가 되지 않은 모양이다. 



위 링크는 모두 Adobe Bugs 관리 시스템에 등록된 것이다.(여러분도 가입해서 투표하길 바란다. 버그 시스템을 잘 이용하면 해결하지 못하는 문제도 쉽게 해결할 수 있을지 모른다. ^^)


Event.RENDER가 발생한 중간에 stage.invalidate()가 호출되면 이게 무시가 되나보다. 그래서 동작상으로는 문제없지만 개발자에게는 버그처럼 느껴질 수 있을지 모르겠다. 어쨌든 필자도 이 문제로 고민을 하다가 한가지 해결방법을 찾았는데 그것은 Event.ENTER_FRAME 이벤트를 이용하는 방법이다. 


Event.ENTER_FRAME을 이용해서 stage.invalidate()의 버그를 말끔히 해결할 수 있다. 다음 코드는NotUseInvalidateButton을 Event.ENTER_FRAME을 이용하는 것으로 바꿔본 것이다.


NotUseInvalidateButton.as



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

	/**
	 * Stage.invalidate()를 사용하는 Button.
	 * Event.ENTER_FRAME 로 Stage.invalidate() 버그 우회 
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class UseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		private var isDrawNow:Boolean = false;
		private var isInvalidated:Boolean = false;
				
		public function UseInvalidateButton()
		{
			super();
			
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}		
		
		private function onAddedToStage( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if( isInvalidated )
			{
				isInvalidated = false;
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.addEventListener(Event.ENTER_FRAME, onRender, false, 0, true );
				this.stage.invalidate();
			}
		}
		
		private function onRender(event:Event):void
		{
			if( this.stage )
			{
				this.stage.removeEventListener( Event.RENDER, onRender, false );
				this.stage.removeEventListener( Event.ENTER_FRAME, onRender, false );
			}
			if( isDrawNow )
			{
				isDrawNow = false;
				drawNow();
			}
		}
		
		private function invalidate():void
		{
			if( this.stage )
			{
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.addEventListener(Event.ENTER_FRAME, onRender, false, 0, true );
				this.stage.invalidate();
			}
			else
			{
				isInvalidated = true;	
			}
		}
		
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function drawNow():void
		{
			trace( "[UseInvalidateButton] drawNow" );
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}		
		
	}
}


위에서 stage.addEventListener( Event.ENTER_FRAME )과 stage.removeEventListener( Event.ENTER_FRAME ) 이 추가된 것을 확인하자. 무작정 이벤트를 청취하고 있으면 안되므로 적절하게 삭제도 하고 있다. 


이렇게 처리함으로써 stage.invalidate()가 제대로 작동안하더라도 drawNow()메소드가 호출이 안되는 경우는 없게 된다.



3. fl.core.UIComponent 수정하기 


Flash의 UIComponent 를 보면 이것 stage.invalidate() 때문에 렌더링이 안되는 경우가 종종 발생한다고 한다. 필자는 Flash는 해본적이 없기 때문에 확실하게 모르지만 이것도 Event.ENTER_FRAME 을 이용해 해결할  수 있다. 그 방법은 다음 링크를 참고한다.


How to Fix the Flash CS3 Components


UIComponent의 callLater()가 어떻게 동작하는 것인지 이것을 보고 감을 잡을 수 있을 것이다. 결국  Event.RENDER, Event.ENTER_FRAME 으로 한다!


4. mx.core.UIComponent의 무효화 메소드들


Flex의 UIComponent에는 invalidateDisplayList(), invalidateProperties(), invalidateSize()와 같은 invalidate() 메소드가 존재한다. 이는 앞선 메소드들의 호출에 대해 각각 updateDisplayList(), commitProperties(), measure() 메소드가 다음 렌더링 시점에 호출되도록 하고 있다. 이는 invalidate/validate 패턴이다. 


재미있게도 Flex에서는 Stage.invalidate()에서 처럼 동작하지 않는 버그는 존재하지 않는다. 어떻게 된 것일까?


사실 Flex 내부를 살펴보면 Stage.invalidate() 와 Event.ENTER_FRAME을 이용해 invalidate 계열의 메소드를 처리하도록 되어 있다. UIComponent 부터, LayoutManager, SystemManager 를 들춰보면 Event.RENDER와 Event.ENTER_FRAME을 가지고 invalidate/validate 패턴을 구현하고 있다. (Flex 개발자도 어쩔 수 없었나보다. ㅡㅡ;;) 


이에 대한 자료는 아래 링크를 읽어보면 되겠다.


Flex internals: Setting a button label



Flex만 접해보고 ActionScript 3.0 학습에 소홀히 했다면 절대 이런 문제를 발견할 수 없었을 것이다. 




정리하며


Stage.invalidate()가 호출할 때 때떄로 Event.RENDER가 호출되지 않는 문제는 버그가 아닐 수 있다. 원래 그렇게 만들어졌을지도 모른다. 하지만 개발자는 이것을 버그로 여긴다. 왜냐하면 보통 개발할때는 그런 것은 생각안하고 메뉴얼만 보고 당연히 그러겠지 생각하기 때문이다. 좀 황당하지만 버그 아닌 버그로 Flex 까지 다 까보게 되었다 ㅡㅡ;


Event.RENDER, Stage.invalidate() 모를때는 setTimeout()과 Timer를 이용해 invalidate/validate 패턴을 구현했었다. 아~ 무식은 용감하다. ㅋ



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 

대부분의 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으로도 개발해보고 싶네요.

 

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

 

 

 


페르시아 왕자를 Flash 버전으로 만든겁니다. 제가 만든게 아니라요 ㅎㅎ
우비소프트에서 개발했다고 하네요.
동작방식은 기존 페르시아 왕자 1과 비슷하지만 맵이 다릅니다.
또한 기존보다 더 어렵더군요.

개발은 어떻게 했을까요?
일반 백터 방식으로 하지 않고 순수 BitmapData만 가지고 처리하도록 만들었을 겁니다. 그말은 DisplayObject객체는 거의 사용하지 않았다는 의미입니다. 그래야 저런 그래픽이 나오겠죠.

어쨌든 한번 과거 페르시아 왕자에 푹 빠져보세요.


 

오늘 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를 사용하여 개발의 효율성을 높이는 방법을 이해하는데 목적이 있다. 처음 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는 매우 유용하다. 익혀두고 적절히 활용하면 몸과 마음이 편해지는 좋은 기술이겠다.

 

관련글

 

 

웹서핑하다가 재미있는 flash 게임을 발견해서 소개한다.

 

이름하야 “Kill the puppy!”

 

헉! 강아지를 죽이는 게임이다. (너무 잔인해서 19금이다. ㅋ)

이 게임은 보통 Flash 게임과 달리 웹캠을 이용한 게임이다. 해보면 알겠지만 매우 독특하면서 재미도 있다. 화면의 모션감지를 이용해서 나오는 강아지를 때려 죽이는 거다.(입에 담기도 힘듬 ㅜㅜ)

준비물은 컴퓨터와 웹캠만 있으면 된다. 강아지를 때리는 도구는 머리, 손, 발, 방망이,칼(ㅡㅡ;)어느것이든 상관없다. 어떤 것이든 사용해서 강아지를 죽이길 바란다. 헤드폰은 반드시 착용야 개 때리는 재미를 더할 수 있다. ㅋ

 

아참… 강아지 때린다고 컴퓨터 모니터를 때리면 안습이다.

 ^^;

 

정말 아이디어 죽인다. ^^

 

게임하러 가기

 

http://www.neuroproductions.be/puppy-WebcamGame/

 

 

 

옥상훈님이 운영하시는 개발자 모임인 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)

도전! 개발자 무한 상상력

 

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에 기술문서로 올린 글입니다.

+ Recent posts