Post on 17-Dec-2014
description
(푸딩얼굴인식����������� ������������������ 앱을����������� ������������������ 통해서����������� ������������������ 본)
하이브리드앱아키텍쳐����������� ������������������ 및����������� ������������������ 개발����������� ������������������ 사례
앱스프레소팀����������� ������������������ |����������� ������������������ 장동수
1
index
1. 하이브리드앱����������� ������������������ 아키텍쳐����������� ������������������ 개요
2. 하이브드리앱����������� ������������������ 유형����������� ������������������ 및����������� ������������������ 특징
3. 푸딩얼굴인식����������� ������������������ 앱����������� ������������������ 개발����������� ������������������ 사례����������� ������������������ 공유
4. 앱스프레소����������� ������������������ 플러그인����������� ������������������ 활용
5. Lessons����������� ������������������ Learned
6. References
2
하이브리드앱����������� ������������������ 아키텍쳐����������� ������������������ 개요
이런����������� ������������������ 거...아님����������� ������������������ -_-;
3
하이브리드앱����������� ������������������ 아키텍쳐����������� ������������������ 구성����������� ������������������ 요소
웹����������� ������������������ 표준����������� ������������������ 기술
플랫폼����������� ������������������ SDK
iOS����������� ������������������ SDK
하이브리드
HTML5 CSS 자바스크립트
비표준����������� ������������������ Device����������� ������������������ APIs
안드로이드����������� ������������������ SDK
윈폰7����������� ������������������ SDK …⋯
웹����������� ������������������ UI����������� ������������������ 툴킷 자바스크립트����������� ������������������ 프레임웍/라이브러리
표준����������� ������������������ Device����������� ������������������ APIs
웹네이티브
웹브라우져����������� ������������������ “앱”
프레임웍
네이티브����������� ������������������ 라이브러리
UI����������� ������������������ 툴킷
개발����������� ������������������ 도구 웹브라우져����������� ������������������ “엔진”
4
하이브리드앱의����������� ������������������ 꿈
Development����������� ������������������ Cost
Application����������� ������������������ Quality
네이티브
웹
BEST
WORST
하이브리드
5
하이브리드라는����������� ������������������ 이름의����������� ������������������ “짬뽕”...
난,물~����������� ������������������ H2O~
O
H H
6
네이티브
웹 웹
내가,하이브리드~
앱����������� ������������������ 개발자들을����������� ������������������ 유혹하는����������� ������������������ “파란”����������� ������������������ 짬뽕~
7
웹����������� ������������������ 개발자들을����������� ������������������ 유혹하는����������� ������������������ “빨간”����������� ������������������ 짬뽕~
웹
네이티브 네이티브
나도,하이브리드~
8
네이티브와����������� ������������������ 웹의����������� ������������������ 결합
웹네이티브Native-WebBridge
Java����������� ������������������ Applet?Active-X?
Flash/Flex?
문제는....다리!!
9
네이티브와����������� ������������������ 웹의����������� ������������������ 결합
WebView
WebViewClient����������� ������������������ &����������� ������������������ WebChromeClient
loadUrl
addJavascriptInterface
UIWebView
UIWebViewDelegate
loadRequest
stringByEvaluatingJavascriptFromString
10
네이티브와����������� ������������������ 웹의����������� ������������������ 결합
그림 출처: http://petticoatsandpistols.com/2010/05/12/
URL
자바스크립트
쿠키
캐시그래봤자,문자열~
어차피,꼼수
그리고...HTTP!
11
하이브리드앱����������� ������������������ 유형����������� ������������������ 및����������� ������������������ 특징
12
네이티브����������� ������������������ 지향����������� ������������������ 하이브리드앱
사실상����������� ������������������ 네이티브,
웹은����������� ������������������ 거들����������� ������������������ 뿐...
·제한적이고����������� ������������������ 직관적인����������� ������������������ 네이티브와����������� ������������������ 웹의����������� ������������������ 결합·웹브라우져����������� ������������������ as-a����������� ������������������ UI����������� ������������������ 컴포넌트·도움말,����������� ������������������ 앱/개발사����������� ������������������ 소개,����������� ������������������ 공지사항/새소식...·웹����������� ������������������ 기반����������� ������������������ 사용자����������� ������������������ 인증(OAuth)...
13
웹브라우져����������� ������������������ as-a����������� ������������������ UI����������� ������������������ 컴포넌트
14
웹����������� ������������������ 기반����������� ������������������ 사용자����������� ������������������ 인증
15
예제����������� ������������������ 코드(안드로이드)
<<웹서버 컨텐츠 불러오기>>public class NoticeActivity extends Activity { ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); WebView webView = (WebView)findViewById(R.id.webView); webView.getSettings().setJavaScriptEnabled(true); webView.setWebChromeClient(new WebChromeClient()); ... webView.loadUrl(“http://m.pudding.kr/pud/mNotice.kth”); ... } ...}
<<앱에 포함된 정적 컨텐츠 불러오기>>public class HelpActivity extends Activity { ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); WebView webView = (WebView)findViewById(R.id.webView); webView.getSettings().setJavaScriptEnabled(true); webView.setWebChromeClient(new WebChromeClient()); ... webView.loadUrl(“file:///android_asset/www/help.html”); ... } ...}
16
예제����������� ������������������ 코드(iOS)
<<웹 서버 컨텐츠 불러오기>>@interface NoticeViewController : UIViewController { IBOutlet UIWebView *webView;...@end
@implementation HelpViewController...- (void)viewDidLoad { ... NSURL *url = [NSURL URLWithString:@”http://m.pudding.kr/pud/mNotice.kth”]; NSURLRequest *requestObj = [NSURLRequest requestWithURL:url]; [webView loadRequest:requestObj]; ...}...@end <<앱에 포함된 정적 컨텐츠 불러오기>>
@interface HelpViewController : UIViewController { IBOutlet UIWebView *webView;...@end
@implementation HelpViewController...- (void)viewDidLoad { ... NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; NSString *path = [bundlePath stringByAppendingPathComponent:@”/www/help.html”]; NSURL *url = [NSURL fileURLWithPath:path]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; [webView loadRequest:request]; ...}...@end
17
웹은����������� ������������������ 아니지만����������� ������������������ 네이티브도����������� ������������������ 아닌,
그러나����������� ������������������ 웹스러운...
·광범위하고����������� ������������������ 일관성없는����������� ������������������ 네이티브와����������� ������������������ 웹의����������� ������������������ 결합·웹브라우져를����������� ������������������ 내장한����������� ������������������ 네이티브����������� ������������������ 클라이언트·기존����������� ������������������ 웹����������� ������������������ 서버����������� ������������������ “조금����������� ������������������ 손����������� ������������������ 봐서...”����������� ������������������ 재활용·기존����������� ������������������ 웹����������� ������������������ 컨텐츠����������� ������������������ “조금����������� ������������������ 손����������� ������������������ 봐서...”����������� ������������������ 재활용
“한����������� ������������������ 지붕����������� ������������������ 두����������� ������������������ 가족”����������� ������������������ 하이브리드앱
18
여기도����������� ������������������ 하이브리드~
19
저기도����������� ������������������ 하이브리드~
20
예제����������� ������������������ 코드(안드로이드)
<<링크 클릭 가로채기>>...WebView webView = (WebView)findViewById(R.id.webView);webView.getSettings().setJavaScriptEnabled(true);webView.setWebChromeClient(new WebChromeClient());webView.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading(WebView webView, String url) { if(!url.startsWith(“http://m.pudding.kr/pud/”) { new AndroidDialog.Builder(NoticeActivity.this) .setMessage(“딴데로 갈라구?? -_-+”) .setPositiveButton(“아니... 여기 있을께 ㅠㅠ”, new DialogInterface.OnClickListener() { dialog.dismiss(); }) .setNegativeButton(“갈꼬얌!”, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int witch) { dialog.dismiss(); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); NoticeActivity.this.startActivity(intent); } }).show(); return false; } else if ... ...이러쿵 저러쿵... } else if ... ...어쩌구 저쩌구... } else if ... ...구시렁 구시렁... } view.loadUrl(url); return true; }});webView.loadUrl(“http://m.pudding.kr/pud/mNotice.kth”);...
21
예제����������� ������������������ 코드(iOS)
<<링크 클릭 가로채기>>@interface NoticeViewController : UIViewController<UIWebViewDelegate, UIAlertViewDelegate> { IBOutlet UIWebView *webView; NSString *externalUrl;...@implementation HelpViewController...- (void)viewDidLoad { NSURL *requestUrl = [NSURL URLWithString:@”http://m.pudding.kr/pud/mNotice.kth”]; [webView loadRequest:[NSURLRequest requestWithURL:requestUrl]]; [webView setDelegate:self]}- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *url = [[request URL] absoluteString]; if(![url hasPrefix:@”http://m.pudding.kr/pud/mNotice.kth”]) { self.externalUrl = url; UIAlertView *alertView = [UIAlertView alloc] initWithTitle:nil message:@”딴데로 갈라구?? -_-+” delegate:self cancelButtonTitle:@”아니... 여기 있을께 ㅠㅠ“ otherButtonTitles:@”갈꼬얌!”, nil]; [alertView show]; [alertView release]; return NO; } else if ... ...이러쿵 저러쿵... } else if ... ...어쩌구 저쩌구... } else if ... ...구시렁 구시렁... } return YES;}- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if(buttonIndex == YES && self.externalUrl) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:self.externalUrl]; }}...
22
예제����������� ������������������ 코드(안드로이드)
<<URL을 이용한 네이티브와 웹의 통신>>webView.loadUrl(“javascrpt:getFieldValue(‘userName’)”); // 결과는 나중에... 비동기!! -_-;...webView.loadUrl(“javascrpt:setFieldValue(‘userName’, ‘“ + userName + “‘“);...
webView.setWebViewClient(new WebViewClient() { public boolean shouldOverrideUrlLoading(WebView webView, String url) { if(!url.startsWith(“custom://getFieldValue”) { Uri uri = Uri.parse(url); String fieldId = uri.getQueryParameter(“fieldId”); String fieldValue = uri.getQueryParameter(“fieldValue”); if(fieldId.equals(“userName”)) { userName = fieldValue; // 결과가 도착했다! 이제 어떡하지? 비동기!! OTL } else if ... } else if ... } else if ... // 나는 엘시프가 씨러요! ㅠㅠ } return false; } else if ... } else if ... } else if ... // 나는 엘시프가 씨러요! ㅠㅠ } view.loadUrl(url); return true; }});...
<<URL을 이용한 네이티브와 웹의 통신::자바스크립트>>function getFieldValue(fieldId) { var fieldValue = document.getElementById(fieldId).value; location.href = ‘custom://getFieldValue?fieldId=’ + fieldId + ‘&fieldValue=’ + fieldValue;}function setFieldValue(fieldId, fieldValue) { document.getElementById(fieldId).value = fieldValue;}
23
예제����������� ������������������ 코드(iOS)
<<URL을 이용한 네이티브와 웹의 통신>>NSString *script = [NSString stringWithFormat:@“getFieldValue(‘%@’)”, fieldId];[webView stringByEvaluatingJavaScriptString:script];...
NSString *script = [NSString stringWithFormat:@“setFieldValue(‘%@’, ‘%@‘)“, fieldId, fieldValue];[webView stringByEvaluatingJavaScriptString:script];...
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *url = [[request URL] absoluteString]; if(![url hasPrefix:@”custom://getFieldName”]) { NSDictionary *params = [HttUtils decodeQueryString:[[request URL] query]]; NSString *fieldId = [params objectForKey:@”fieldId”]; NSString *fieldValue = [params objectForKey:@”fieldValue”]; if(fieldId isEqualToString:@”userName”) { self.userName = fieldValue; } else if ... } else if ... } else if ... // 나는 엘시프가 씨러요! ㅠㅠ } [paramArray release]; return NO; } else if ... } else if ... } else if ... // 나는 엘시프가 씨러요! ㅠㅠ } return YES;}...
24
웹����������� ������������������ 지향����������� ������������������ 하이브리드앱
사실상����������� ������������������ 웹,
네이티브는����������� ������������������ 거들����������� ������������������ 뿐...
·광범위하지만����������� ������������������ 일관성있는����������� ������������������ 네이티브와����������� ������������������ 웹의����������� ������������������ 결합����������� ������������������ ·클라이언트����������� ������������������ 사이드����������� ������������������ “웹앱”·기존����������� ������������������ 웹����������� ������������������ 서버����������� ������������������ +����������� ������������������ RESTful����������� ������������������ API����������� ������������������ 서버·기본적인����������� ������������������ 웹����������� ������������������ 컨텐츠는����������� ������������������ 앱에����������� ������������������ 포함
25
하이브리드����������� ������������������ 모바일����������� ������������������ 앱����������� ������������������ 프레임웍
26
이것도����������� ������������������ 하이브리드!
27
푸딩얼굴인식앱����������� ������������������ 개발����������� ������������������ 사례����������� ������������������ 공유
28
“푸딩얼굴인식����������� ������������������ 앱”����������� ������������������ 소개
����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ 푸딩얼굴인식����������� ������������������ 앱은...
·600만+����������� ������������������ 다운로드!·@iolothebard����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ 와����������� ������������������ @seti222·5500+����������� ������������������ 줄의����������� ������������������ 자바스립트·2700+����������� ������������������ 줄의����������� ������������������ CSS·200+����������� ������������������ 줄의����������� ������������������ 네이티브����������� ������������������ 코드·앱스프레소����������� ������������������ 0.9+����������� ������������������ 내부����������� ������������������ 개발����������� ������������������ 버전
29
단일����������� ������������������ 페이지����������� ������������������ 인터페이스
index.html
ActivePage
show/hide
#pageId
pageId.css
pageId.js
30
단일����������� ������������������ 페이지����������� ������������������ 인터페이스
웹앱도����������� ������������������ MVC가����������� ������������������ 필요해!
자바스크립트가����������� ������������������ 컨트롤러!
·웹����������� ������������������ 서버도����������� ������������������ 없는����������� ������������������ 데...����������� ������������������ 페이지����������� ������������������ 이동은����������� ������������������ 왜?!· (GUI)����������� ������������������ 애플리케이션����������� ������������������ 스타일의����������� ������������������ “상태”����������� ������������������ 관리·빠른����������� ������������������ 화면����������� ������������������ 전환����������� ������������������ &����������� ������������������ 화면����������� ������������������ 전환����������� ������������������ 효과·체감����������� ������������������ 성능����������� ������������������ UP!·메모리����������� ������������������ 사용량����������� ������������������ UP!·그런데...����������� ������������������ 공동����������� ������������������ 작업은����������� ������������������ 어떻게?����������� ������������������ -_-;
31
단일����������� ������������������ 페이지����������� ������������������ 인터페이스
<<HTML마크업: section#faceFoundPage>><section id=”faceFoundPage” class=”jj-page”> <header> <button class=”jj-left jj-back”><span data-nls=”common.back”>이전</span></button> <button class=”jj-right jj-home”><span data-nls=”common.home”>홈</span></button> <h1><span data-nls=”faceFoundPage.title”>얼굴인식 결과</span></h1> </header> <article> ... </article> <footer> ... </footer></section>
<<CSS스타일:faceFoundPage.css>>#faceFoundPage { background-color:rgb(255,255,255);}#faceFoundPage > article, #faceFoundPage > footer { background-color:rgb(138,135,136);}...
<<자바스크립트:faceFoundPage.js>>var jj.ui = jj.require(‘jj.ui’);export.FaceFoundPage = new jj.defclass(jj.ui.Page, { onInit: function() { $(this.pageNode).bind(‘onpagebeforeshow’, function() { … }); $(this.pageNode).bind(‘onpageaftershow’, function() { … }); $(this.pageNode).bind(‘onpagebeforehide’, function() { … }); $(this.pageNode).bind(‘onpageaftershow’, function() { … }); ... }, ...});
<<자바스크립트를 이용한 화면 전환>>// 화면을 수동으로 초기화var faceFoundPage = new FaceFoundPage( $(‘#faceFoundPage’), // 화면을 구성하는 DOM 노드 facePickerPage, // 화면내의 .jj-back 노드를 클릭할 때 전환될 페이지 mainPage); // 화면내의 .jj-home 노드를 클릭할 때 전환될 페이지// 화면 표시에 필요한 상태 정보를 전달faceFoundPag.setFaceInfo(faceInfo);// 현재 화면이 왼쪽으로 사라지면서, 새 화면이 오른쪽에서(slideleft) 나타남faceFoundPage.show(‘slideleft’);// 현재 화면을 유지한 채, 새 화면을 아래에서 위로(slideup) 나타남(modal)//faceFoundPage.show(‘slideup’, true);
32
“Placeholder”����������� ������������������ HTML����������� ������������������ Markup
HTML
TemplateData
API����������� ������������������ 서버
+
웹����������� ������������������ 서버
이름 클래스 레벨
<<foreach>><<foreach>><<foreach>>
★ ♥ ♦<<end>><<end>><<end>>
★ ♥ ♦iolo bard 만렙
장동수 개발자 쪼렙
... ... ...
캐시 앱
Placeholder
자바스크립트
이름 클래스 레벨
iolo bard 만렙
장동수 개발자 쪼렙
... ... ...
캐시 로컬스토리지Static����������� ������������������ HTML����������� ������������������ Fragment
33
“Placeholder”����������� ������������������ HTML����������� ������������������ Markup
웹앱도����������� ������������������ MVC가����������� ������������������ 필요해!
뷰와����������� ������������������ 모델의����������� ������������������ 분리
·HTML����������� ������������������ 마크업을����������� ������������������ 위한����������� ������������������ “변수”·웹����������� ������������������ 서버����������� ������������������ 개발에서����������� ������������������ 널리����������� ������������������ 쓰이는����������� ������������������ 템플릿(velocity,����������� ������������������ smarty,����������� ������������������ ...)을����������� ������������������ 웹����������� ������������������ 클라이언트에����������� ������������������ 적용
·서버����������� ������������������ 부하는����������� ������������������ DOWN!·데이터����������� ������������������ 전송량도����������� ������������������ DOWN!·캐시����������� ������������������ 히트율은����������� ������������������ UP!
34
“Placeholder”����������� ������������������ HTML����������� ������������������ Markup
<div id=”faceFound_starTemplate” class=”jj-template”> <div class=”face” data-fastclick=”true”></div> <div class=”rank”></div> <p class=”rate”><span class=”rateText”></span><small>%</small></p> <p class=”nameText” data-fastclick=”true”></p> <p class=”info”></p> <p class=”descriptionText”></p></div>
<div id=”faceFound_star_wrapper”><div id=”faceFound_star_scroller”> <ul> <li class=”nomatch”> <div id=”faceFound_star_nomatch”> <h5 data-nls=”0ah002”>닮은 연예인을 찾지 못했습니다.</h5> <p data-nls=”0ah003”>얼굴이 가까이 나온<br />정면 사진으로 다시 시도해 보세요.</p> <div> <button id=”faceFound_retryBtn” class=”y” data-fastclick=”true”> <span data-nls=”0ah005”>다시 찾기</span> </button> </div> </div> </li> <li> <div id=”faceFound_star1” class=”jj-placeholder” data-template=”#faceFound_starTemplate”></div> </li> <li> <div id=”faceFound_star2” class=”jj-placeholder” data-template=”#faceFound_starTemplate”></div> <div id=”faceFound_star3” class=”jj-placeholder”></div> </li> <li> <div id=”faceFound_star4” class=”jj-placeholder” data-template=”#faceFound_starTemplate”></div> <div id=”faceFound_star5” class=”jj-placeholder” data-template=”#faceFound_starTemplate”></div> </li> </ul></div><!-- star_scroller --></div><!-- star_wrapper -->
35
“Marker”����������� ������������������ CSS����������� ������������������ Class
웹앱도����������� ������������������ MVC가����������� ������������������ 필요해!
뷰와����������� ������������������ 컨트롤러의����������� ������������������ 분리
·CSS����������� ������������������ 스타일시트를����������� ������������������ 위한����������� ������������������ “조건문”·프론트엔드����������� ������������������ UI����������� ������������������ 개발자와����������� ������������������ 자바스크립트����������� ������������������ 개발자의����������� ������������������ 약속·화면의����������� ������������������ 동적인����������� ������������������ 변화를����������� ������������������ 자바스크립트없이����������� ������������������ 확인����������� ������������������ &����������� ������������������ 제어·HTML/CSS는����������� ������������������ “쬐끔”����������� ������������������ 복잡해지고...����������� ������������������ -_-;·자바스크립트는����������� ������������������ “쬐끔”����������� ������������������ 단순해지고...����������� ������������������ -_-;·함께����������� ������������������ 일하기는����������� ������������������ 더����������� ������������������ 좋아지고~����������� ������������������ ^O^
36
“Marker”����������� ������������������ CSS����������� ������������������ Class
<<스타일시트>>/* 일치하는 연예인이 하나라도 있으면 안내문을 표시하지 않는다 */#faceFoundPage .nomatch { display:none;}/* 일치하는 연예인이 없으면 그에 따른 안내문을 표시한다 */#faceFoundPage.nomatch .nomatch { display:block;}/* 일치하는 연예인이 없으면 carousel 페이지 인디케이터를 숨긴다 */#faceFoundPage.nomatch ul > li { display:none;}/* 일치하는 연예인이 없으면 하단의 공유 버튼들을 비활성화 */#faceFoundPage.nomatch footer button { opacity:0.5;}/* 일치하는 연예인 숫자에 따라 carousel 영역(iscroll)의 너비를 조절한다#faceFoundPage.nomatch #faceFound_star_scroller, #faceFoundPage.match_1 #faceFound_star_scroller { width:320px;/*320*1pages*/ } #faceFoundPage.match_2 #faceFound_star_scroller, #faceFoundPage.match_3 #faceFound_star_scroller { width:640px;/*320*2pages*/ }#faceFoundPage.match_4 #faceFound_star_scroller,#faceFoundPage.match_5 #faceFound_star_scroller { width:960px;/*320x3pages*/}
<<자바스크립트>>if(matchingCelebs < 1) { $(‘#faceFoundPage’).addClass(‘nomatch’);} else { $(‘#faceFoundPage’).removeClass(‘nomatch’).addClass(‘match_’ + matchingCelebs);}
37
단말����������� ������������������ 해상도별����������� ������������������ 최적화
Responsive����������� ������������������ Web����������� ������������������ Design?
지금은����������� ������������������ 곤란하니,����������� ������������������ 기다려����������� ������������������ 달라~
·<HTML>����������� ������������������ 태그에����������� ������������������ 마커����������� ������������������ CSS����������� ������������������ 클래스����������� ������������������ 추가/활용·기본적으로����������� ������������������ 해상도에����������� ������������������ 자유로운����������� ������������������ 프론트엔드����������� ������������������ UI����������� ������������������ 개발·널리����������� ������������������ 쓰이는����������� ������������������ 해상도는����������� ������������������ 꼼꼼하게����������� ������������������ “미세”����������� ������������������ 조정·특이한����������� ������������������ 해상도는����������� ������������������ 최소한의����������� ������������������ “미세”����������� ������������������ 조정·이����������� ������������������ 한����������� ������������������ 몸����������� ������������������ 희생해서...����������� ������������������ 모두가����������� ������������������ 행복할����������� ������������������ 수����������� ������������������ 있다면...����������� ������������������ ㅠㅠ
38
“Marker”����������� ������������������ CSS����������� ������������������ Class
<<단말 해상도 마커 클래스를 활용한 스타일 최적화>>/* 해상도 독립적인 레이아웃 */section { width:100%; height:100%; }header, footer { height:10%; }article { height:80%; }article.noheader, article.nofooter { height:90%; }article.noheader.nofooter { height:100%; }.../* 아이폰 해상도에 맞춰 미세 조정 */.screen320x480 header { height:44px; }.screen320x480 footer { height:49px; }.screen320x480 article { height:387px;/*480-44-49*/ }.screen320x480 article.noheader { height:436px;/*480-44*/ }.screen320x480 article.nofooter { height:431px;/*480-49*/ }.../* 갤러시탭에 해상도에 맞춰 미세 조정 */.screen600x1024 header { height:80px; }...
<<단말 플랫폼/해상도 마커 클래스 초기화 스크립트>>var platformCls = (/iOS/.test(navigator.userAgent)) ? ‘ios’ : (/Android/.test(navigator.userAgent) ? android : ‘generic’);var screenCls =‘screen’, screen.width, ‘x’, screen.height).join(‘’);var orientationCls = (screen.width < screen.height) ? ‘portrait’ : ‘landscape’;$(window).addClass(platformCls, screenCls, orientationCls);
<<단말 플랫폼 마커 클래스를 활용한 스타일 최적화>>/* 플랫폼 고유의 체크박스(on/off 스위치) 이미지를 사용 */input[type=”checkbox”] { background-repeat:no-repeat; background-size:100%;}.ios input[type=”checkbox”] { background-image:@url(‘img/ios/check.png’);}.android input[type=”checkbox”] { background-image:@url(‘img/android/check.png’);}...
39
다국어����������� ������������������ 처리
I18N?����������� ������������������ L10N?����������� ������������������ A11Y?
이제는����������� ������������������ 선택이����������� ������������������ 아닌����������� ������������������ 필수!
·<HTML>����������� ������������������ 태그에����������� ������������������ 마커����������� ������������������ CSS����������� ������������������ 클래스����������� ������������������ 추가/활용· “그림����������� ������������������ 글자”����������� ������������������ 사용����������� ������������������ 최소화����������� ������������������ :����������� ������������������ 국제화����������� ������������������ &����������� ������������������ 접근성·일관성있는����������� ������������������ 번역어����������� ������������������ 식별자·언어별����������� ������������������ 어순����������� ������������������ /����������� ������������������ 길이����������� ������������������ /����������� ������������������ 너비����������� ������������������ 차이����������� ������������������ 고려·언어별����������� ������������������ UI����������� ������������������ “미세”����������� ������������������ 조정
40
다국어����������� ������������������ 처리
41
다국어����������� ������������������ 처리
<<다국어 지원 초기화 스크립트>>var lang = navigator.language.substring(0, 2);if(lang !== ‘en’ && lang !== ‘ja’ && lang !== ‘zh’) { lang = ‘en’; }$.get(‘locales/’ + lang + ‘/messages.json’, function(messages) { $(document.documentElement).addClass(‘jj-nls-’ + lang);//언어 식별 마커 클래스 추가 $.each(document.body).find(‘*[data-nls]’).each(function(index, node) { var key = node.attr(‘data-nls’); var message = messages[key]; if(message) { node.html(message); } else { console.error(’missing nls message:’ + key); }});
<<다국어 번역 텍스트 파일(locales/언어/messages.json)>>“common.back”: “Back”,...“setupTwitterPage.title”: “Setup - Twitter”,...
<<다국어 지원 자리 잡기 태그>><button><span data-nls=”common.back”>이전</span></button>...<h1><span data-nls=”setupTwitterPage.title”>설정 - 트위터</span></h1>...
<<다국어 번역 텍스트 치환 결과>><button><span data-nls=”common.back”>Back</span></button>...<h1><span data-nls=”setupTwitterPage.title”>Setup - Twitter</span></h1>...
<<마커 클래스를 활용한 언어별 스타일 최적화>>.jj-nls-zh #intro_title { /*locales/zh/img/intro_title.png*/ background-image:@url(‘../img/intro_title.png’);}.jj-nls-zh #find_cameraBtn > .text,.jj-nls-zh #find_albumBtn > .text {! width:70px; display:inline-block;}...
42
Ant를����������� ������������������ 이용한����������� ������������������ 빌드����������� ������������������ 자동화
1.����������� ������������������ verify:����������� ������������������ jslint
2.����������� ������������������ merge:����������� ������������������ ant����������� ������������������ concat
3.����������� ������������������ compress:����������� ������������������ YUICompressor
4.����������� ������������������ preprocess:����������� ������������������ ant����������� ������������������ filter
5.����������� ������������������ test:����������� ������������������ JSTestDriver
6.����������� ������������������ docs:����������� ������������������ JSDocToolkit(v2)
43
Ant를����������� ������������������ 이용한����������� ������������������ 빌드����������� ������������������ 자동화
<target name=”verify_js” depends=”init”> <jslint jslint=”${jslint.js}” encoding=”${js.encoding}” options=”${jslint.options}” haltOnFailure=”${jslint.haltOnFailure}”> <predef>${jslint.predef}</predef> <formatter type=”plain”/> <filelist refid=”js.src.files”/> </jslint> </target>
<target name=”merge_js” depends=”verify_js” if=”build.release”> <!-- pudface --> <concat destfile=”${js.out.merged}” encoding=”${js.encoding}” outputencoding=”${js.encoding}” fixlastline=”no” eol=”unix”> <filelist refid=”js.src.files”/> <filterchain> <deletecharacters chars=””/> </filterchain> </concat> <echo message=”merged into ${js.out.merged}”/> </target>
<target name=”compress_js_yuicompressor” depends=”verify_merged_js” if=”build.release”> <!-- pudface --> <java classname=”${yuicompressor.mainclass}” classpathref=”yuicompressor.classpath” fork=”true” failonerror=”true”> <arg value=”--verbose”/> <arg value=”--charset”/> <arg value=”${js.encoding}”/> <arg value=”--type”/> <arg value=”js”/> <arg value=”-o”/> <arg file=”${js.out.compressed}”/> <arg file=”${js.out.merged}”/> </java> <delete file=”${js.out.merged}”/> <echo message=”compressed into ${js.out.compressed}”/> </target>
44
Ant를����������� ������������������ 이용한����������� ������������������ 빌드����������� ������������������ 자동화
<target name=”build_debug” depends=”clean,copy_apis” if=”build.debug”> <copy todir=”${out.dir}” verbose=”true”> <fileset dir=”${src.dir}”> <exclude name=”index.html”/> </fileset> </copy> <copy todir=”${out.dir}” encoding=”${html.encoding}” outputencoding=”${html.encoding}” verbose=”true” overwrite=”true”> <fileset dir=”${src.dir}”> <include name=”index.html”/> </fileset> <filterchain> <linecontains negate=”true”><contains value=”@@RELEASE”/></linecontains> </filterchain> </copy> </target>
<target name=”build_release” depends=”clean,compress_js,compress_css” if=”build.release”> <copy todir=”${out.dir}” verbose=”true”> <fileset dir=”${src.dir}”> <exclude name=”index.html”/> <exclude name=”js/pudface/**”/> <exclude name=”css/**”/> </fileset> </copy> <copy todir=”${out.dir}” encoding=”${html.encoding}” outputencoding=”${html.encoding}” verbose=”true” overwrite=”true”> <fileset dir=”${src.dir}”> <include name=”index.html”/> </fileset> <filterchain> <linecontains negate=”true”><contains value=”@@DEBUG”/></linecontains> </filterchain> </copy> </target>
45
네이티브와의����������� ������������������ 결합
사용한����������� ������������������ 앱스프레소����������� ������������������ 플러그인
·deviceapis.filesystem:����������� ������������������ 파일����������� ������������������ 입출력·deviceapis.deviceinteraction:����������� ������������������ 화면����������� ������������������ 꺼짐����������� ������������������ 방지����������� ������������������ /����������� ������������������ 진동·ax.ext.media:����������� ������������������ 카메라����������� ������������������ /����������� ������������������ 포토����������� ������������������ 앨범����������� ������������������ /����������� ������������������ 효과음·ax.ext.net:����������� ������������������ 업로드����������� ������������������ /����������� ������������������ 다운로드·ax.ext.ui:����������� ������������������ 네이티브����������� ������������������ UI����������� ������������������ /����������� ������������������ 차일드����������� ������������������ 브라우져·ax.ext.ga:����������� ������������������ 구글����������� ������������������ 통계·ax.ext.admob:����������� ������������������ 애드몹����������� ������������������ 광고· kth.puddingface:����������� ������������������ 푸딩얼굴인식앱����������� ������������������ 전용����������� ������������������ *^^*
46
앱스프레소����������� ������������������ 플러그인����������� ������������������ 활용
47
Android����������� ������������������ Native����������� ������������������ Module(Android����������� ������������������ Library����������� ������������������ Project)
iOS����������� ������������������ Native����������� ������������������ Module(Xcode����������� ������������������ Static����������� ������������������ Library����������� ������������������ Project)
Appspresso����������� ������������������ Plugin����������� ������������������ Project
����������� ������������������ 앱스프레소����������� ������������������ 플러그인����������� ������������������ 구조
export&����������� ������������������ share
link&����������� ������������������ run
Appspresso����������� ������������������ Application����������� ������������������ Project
Appspresso����������� ������������������ Plugin����������� ������������������ Archive����������� ������������������ (*.axp)
axplugin.xml axplugin.js
res overlay
lib*.a *.jar
48
앱스프레소����������� ������������������ 플러그인����������� ������������������ API
AxPlugin
1
2
2
4
/*����������� ������������������ YOUR����������� ������������������ CODE����������� ������������������ HERE����������� ������������������ */activate(AxRuntimeContext)deactivate(AxRuntimeContext)execute(AxPluginContext)
...
AxRuntimeContext
getWebView()getWidget()
...requirePlugin(pluginId)executeJavaScript(script)
...
AxPluginContext
int����������� ������������������ getId()String����������� ������������������ getMethod()Object[]����������� ������������������ getParams()
...sendResult([result])
sendError(code[,����������� ������������������ message])
자바스크립트����������� ������������������ API
플랫폼����������� ������������������ 확장����������� ������������������ API
Android iOS
49
앱스프레소����������� ������������������ 플러그인����������� ������������������ 플랫폼����������� ������������������ 확장����������� ������������������ API
·안드로이드����������� ������������������ 전용·Activity·ActivityListener·WebViewListener·WebViewClientListener/WebChromeClientListener·iOS����������� ������������������ 전용·UIViewController·UIApplicationDelegate·UIWebViewDelegate·AxViewControllerDelegate
50
앱스프레소����������� ������������������ 플러그인����������� ������������������ 자바스크립트����������� ������������������ API
·AxPlugin의����������� ������������������ 자바스크립트����������� ������������������ “stub”· function����������� ������������������ execSync(method,����������� ������������������ params)· function����������� ������������������ execAsync(method,����������� ������������������ successCallback,����������� ������������������ errorCallback,����������� ������������������ params)
· params:����������� ������������������ array����������� ������������������ of����������� ������������������ arguments
· successCallback:����������� ������������������ function(result)����������� ������������������ {����������� ������������������ ...����������� ������������������ }
· errorCallback:����������� ������������������ function(error)����������� ������������������ {����������� ������������������ ...����������� ������������������ }
·ax,����������� ������������������ ax.error,����������� ������������������ ax.util,����������� ������������������ ax.console,����������� ������������������ ax.request,����������� ������������������ ax.bridge,����������� ������������������ ax.plugin,����������� ������������������ ...
51
앱스프레소����������� ������������������ 플러그인����������� ������������������ 개발����������� ������������������ 실습
네이티브����������� ������������������ 주소록����������� ������������������ UI����������� ������������������ 플러그인
·deviceapis.pim.contact����������� ������������������ 는...·너무����������� ������������������ 어려워����������� ������������������ +����������� ������������������ 너무����������� ������������������ 느려����������� ������������������ +����������� ������������������ 뽀대도����������� ������������������ 안나...����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ x3·그래서,����������� ������������������ @bluenmad ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ ����������� ������������������ 가����������� ������������������ 만들었습니다~
·어떻게?
ax.ext.contact.pickContact(function(contact) { if(contact && contact.phoneNumbers) { var firstPhoneNumber = phoneNumbers.split(‘,’)[0]; if(confirm(contact.firstName + ‘에게 전화걸까요?’)) { location.href = ‘tel:’ + firstPhoneNumber; } }}, function(error) { ... })
52
axplugin.xml
<?xml version=”1.0” encoding=”UTF-8”?><axplugin id=”ax.ext.contact” version=”1.0”>! <description>Contact Extension API Appspresso Plugin! </description>! <url>http://appspresso.com</url>! <author>Appspresso Dev. Team</author>! <license>Copyright (c) 2011, KT Hitel Co., LTD. All Rights Reserved.! </license>
! <feature id=”http://appspresso.com/api/ax.ext.contact“! ! category=”Extension” />
! <module platform=”android” platform-version=”8”! ! min-platform-version=”7” max-platform-version=””! ! class=”com.appspresso.screw.contact.ContactPlugin”>! ! <property name=”permission” value=”android.permission.READ_CONTACTS” />! </module>
! <module platform=”ios” platform-version=”4.1”! ! min-platform-version=”4.0” max-platform-version=””! ! class=”ax_ext_contact_MyPlugin”>! ! <property name=”framework” value=”AddressBook.framework, AddressBookUI.framework” />! </module></axplugin>
53
axplugin.js
/*jslint browser:true, confusion:true, debug:true, devel:true, nomen:true, plusplus:true, vars:true *//** * @fileOverview Contact Extension API * @author blueNmad * @version 1.0 */(function () { “use strict”;
var NS_CONTACT = “ax.ext.contact”; var PREFIX_CONTACT = “ax.ext.contact”;
/** * Contact Extension API * * @namespace * @name ax.ext.contact */
/** * @class * @name ContactOpts * @memberOf ax.ext.contact */
/** * native callback for pickContact. * * @param result * @memberOf ax.ext.contact * @private */ var onPickContactCallback = undefined;
/** * pick a contact * * @param {function} callback * @param {function} errback * @param {ax.ext.contact.ContactOpts} opts * @return AxRequest * @methodOf ax.ext.contact */
function pickContact(callback, errback, opts) {
onPickContactCallback = callback;
return this.execAsync(‘pickContact’, ax.nop, errback, [opts || {}]);
}
function onPickContact(contact) { if ( !! onPickContactCallback) { onPickContactCallback(eval(contact)); }
onPickContactCallback = undefined; }
ax.plugin(PREFIX_CONTACT, { ‘pickContact’: pickContact, ‘onPickContact’: onPickContact }, NS_CONTACT);})();
54
com...contact.ContactPlugin.java
package com.appspresso.screw.contact;
import android.app.Activity;import android.content.Intent;import android.provider.ContactsContract;
import com.appspresso.api.AxPluginContext;import com.appspresso.api.AxRuntimeContext;import com.appspresso.api.DefaultAxPlugin;...<<중간생략>>...
/** * Appspresso Plugin Android Module * * id: ax.ext.contact * version: 1.0.0 * */public class ContactPlugin extends DefaultAxPlugin { private static final int
REQ_PICK_CONTACT = 62000;
private ActivityListener activityListener = new ActivityAdapter() {
public boolean onActivityResult(Activity activity, int requestCode, int
resultCode, Intent data) { if (ContactPlugin.REQ_PICK_CONTACT ==
requestCode && data != null) { return ContactUtils.onPickContact(
runtimeContext, data); }
return super.onActivityResult(activity, requestCode, resultCode, data);
} }; public void activate(
AxRuntimeContext runtimeContext) { super.activate(runtimeContext);
runtimeContext.addActivityListener(activityListener);
}
public void deactivate(AxRuntimeContext runtimeContext) {
runtimeContext.removeActivityListener(activityListener);
super.deactivate(runtimeContext); }
public void pickContact(AxPluginContext context) {
Intent intent = new Intent(Intent.ACTION_PICK,
ContactsContract.Contacts.CONTENT_URI); runtimeContext.getActivity()
.startActivityForResult(intent, REQ_PICK_CONTACT);
context.sendResult(); }}
55
com...contact.ContactUtils.java
package com.appspresso.screw.contact;
import java.util.ArrayList;import java.util.List;
import org.apache.commons.logging.Log;import org.json.JSONArray;import org.json.JSONObject;
import android.app.Activity;import android.content.Intent;import android.database.Cursor;import android.provider.ContactsContract;...<<중략>>...import android.webkit.WebView;
import com.appspresso.api.AxLog;
class ContactUtils { private static Log L =
AxLog.getLog(“ContactPlugin”);
private static final String JS_CALLBACK_ONPICKCONTACT =
“ax.ext.contact.onPickContact”;
public static boolean onPickContact(RuntimeContext runtimeContext,
Intent data) { if (data == null) { return false; }
String contactId = data.getData().getLastPathSegment();
JSONObject contact = ContactUtils.getContactWithContactId(
activity, contactId);
if (contact != null) { runtimeContex.invokeJavaScriptFunction(
JS_CALLBACK_ONPICKCONTACT, contact); } return true; }
static JSONObject getContactWithContactId(Activity activity, String contactId) {
JSONObject contact = new JSONObject(); ... Cursor cursor = null; Cursor rawContactIdsCursor = null; try { String[] rawContactIds = null; rawContactIdsCursor = activity .getContentResolver().query( RawContacts.CONTENT_URI, new String[] { RawContacts._ID }, RawContacts.CONTACT_ID + “ = ?”, new String[] { contactId }, null); ... <<중략>> ... }}
56
ax_ext_contact_MyPlugin.h
#import <Foundation/Foundation.h>#import <UIKit/UIKit.h>#import <AddressBook/AddressBook.h>#import <AddressBookUI/AddressBookUI.h>#import “AxPlugin.h”
@protocol AxContext;@protocol AxPluginContext;
@interface ax_ext_contact_MyPlugin : NSObject<AxPlugin, ABPeoplePickerNavigationControllerDelegate> {@private NSObject<AxRuntimeContext> *_runtimeContext;}
@property (nonatomic,readonly,retain) NSObject<AxRuntimeContext>* runtimeContext;
- (void)activate:(NSObject<AxRuntimeContext>*)runtimeContext;- (void)deactivate:(NSObject<AxRuntimeContext>*)runtimeContext;- (void)execute:(NSObject<AxPluginContext>*)context;
- (IBAction)presentABPeoplePickerNavigationController;
// ABPeoplePickerNavigationControllerDelegate method- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person;
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier;
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;
@end
57
ax_ext_contact_MyPlugin.m
//// ax_ext_contact_MyPlugin.m//// Copyright 2011 none. All rights reserved.//
#import “AxRuntimeContext.h”#import “AxPluginContext.h”#import “AxError.h”#import “AxLog.h”#import “ax_ext_contact_MyPlugin.h”
#define JS_CALLBACK_ONPICKCONTACT @”ax.ext.contact.onPickContact”
@implementation ax_ext_contact_MyPlugin
@synthesize runtimeContext = _runtimeContext;
- (void)activate: (NSObject<AxRuntimeContext>*)runtimeContext {
_runtimeContext = [runtimeContext retain];}
- (void)deactivate:(NSObject<AxRuntimeContext>*)runtimeContext {
[_runtimeContext release]; _runtimeContext = nil;}
- (void)execute:(id<AxPluginContext>)context { NSString* method = [context getMethod]; AX_LOG_TRACE(@”ContactView_ios_method : %s”, method); if([method isEqualToString:@”pickContact”]){
[self presentABPeoplePickerNavigationController]; [context sendResult]; } else { [context sendError:AX_NOT_AVAILABLE_ERR]; }}
- (IBAction)presentABPeoplePickerNavigationController { dispatch_async(dispatch_get_main_queue(), ^{ ABPeoplePickerNavigationController *picker = [[[ABPeoplePickerNavigationController alloc] init] autorelease]; picker.peoplePickerDelegate = self; [[self.runtimeContext getViewController] presentModalViewController:picker animated:YES]; // [picker release]; });}...<<중략>>...- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker { [[self.runtimeContext getViewController] dismissModalViewControllerAnimated:YES];}
@end
58
Lessons����������� ������������������ Learned59
to.����������� ������������������ ����������� ������������������ 개발자
[경고]����������� ������������������ 이����������� ������������������ 웹은����������� ������������������ 당신이����������� ������������������ 알았던����������� ������������������ 그����������� ������������������ 웹이����������� ������������������ 아닙니다.����������� ������������������
The����������� ������������������ Web����������� ������������������ is����������� ������������������ Dead.
크로스����������� ������������������ 플랫폼?����������� ������������������ 새로운����������� ������������������ 플랫폼!!
이����������� ������������������ 자바스크립트는����������� ������������������ 당신이����������� ������������������ 알았던����������� ������������������
그����������� ������������������ 자바스크립트가����������� ������������������ 아닙니다.
웹����������� ������������������ 요소����������� ������������������ 기술����������� ������������������ +����������� ������������������ (GUI)����������� ������������������ 애플리케이션����������� ������������������ 아키텍쳐
...
60
cc.����������� ������������������ 기획자,����������� ������������������ 디자이너,����������� ������������������ ...
[경고]����������� ������������������ 이����������� ������������������ 웹은����������� ������������������ 당신이����������� ������������������ 알았던����������� ������������������ 그����������� ������������������ 웹이����������� ������������������ 아닙니다.
The����������� ������������������ Web����������� ������������������ is����������� ������������������ Dead.
저비용����������� ������������������ 고품질?!
웹의����������� ������������������ 한계,����������� ������������������ 장점,����������� ������������������ 단점을����������� ������������������ 고려한����������� ������������������ 기획����������� ������������������ &����������� ������������������ 디자인과
적절한����������� ������������������ 품질����������� ������������������ 목표����������� ������������������ 설정
무작정����������� ������������������ 네이티브����������� ������������������ 앱의����������� ������������������ UI/UX����������� ������������������ 따라하기����������� ������������������ 금지!
...
61
References
· 하이브리드����������� ������������������ 모바일����������� ������������������ 앱����������� ������������������ 프레임웍����������� ������������������ http://slideshare.net/iolo/hybrid-mobile-application-framework
· 단일����������� ������������������ 페이지����������� ������������������ 인터페이스����������� ������������������ 웹/앱����������� ������������������ 개발����������� ������������������ http://slideshare.net/iolo/ss-7719322
· Android����������� ������������������ SDK����������� ������������������ WebView����������� ������������������ 레퍼런스����������� ������������������ http://goo.gl/iqr9H
· iOS����������� ������������������ SDK����������� ������������������ UIWebView����������� ������������������ 레퍼런스����������� ������������������ http://goo.gl/U8XGy
· Android용����������� ������������������ 하이브리드����������� ������������������ 앱����������� ������������������ 템플릿����������� ������������������ https://github.com/iolo/hellowebapp-android
· How����������� ������������������ to����������� ������������������ build����������� ������������������ Android����������� ������������������ App����������� ������������������ with����������� ������������������ HTML/CSS/JavaScript����������� ������������������ http://youtube.com/watch?v=uVqp1zcMfbE
· iOS용����������� ������������������ 하이브리드����������� ������������������ 앱����������� ������������������ 템플릿����������� ������������������ https://github.com/iolo/hellowebapp-ios
· How����������� ������������������ to����������� ������������������ build����������� ������������������ iOS����������� ������������������ App����������� ������������������ with����������� ������������������ HTML/CSS/JavaScript����������� ������������������ http://youtube.com/watch?v=L28lGkoSQ2c
· 푸딩����������� ������������������ 얼굴����������� ������������������ 인식����������� ������������������ 앱(Android)����������� ������������������ https://market.android.com/details?id=com.kth.puddingface
· 푸딩����������� ������������������ 얼굴����������� ������������������ 인식����������� ������������������ 다국어����������� ������������������ 앱(iOS)����������� ������������������ http://itunes.apple.com/us/app/id378461555?mt=8
· 앱스프레소����������� ������������������ 홈페이지����������� ������������������ http://appspresso.com/
· 폰갭����������� ������������������ 홈페이지����������� ������������������ http://phonegap.com/
· 티타늄����������� ������������������ 홈페이지����������� ������������������ http://appcelerator.com/
· jQuery����������� ������������������ 홈페이지����������� ������������������ http://jquery.com/
· iScroll����������� ������������������ 홈페이지����������� ������������������ http://cubiq.org/iscroll/
· JSLint����������� ������������������ 홈페이지����������� ������������������ http://jslint.com/
· YUICompressor����������� ������������������ 홈페이지����������� ������������������ http://developer.yahoo.com/yui/compressor/
62
That’s����������� ������������������ all����������� ������������������ folks...63
감사합니다모바일개발실 / 앱스프레소팀 / 장동수
iolothebard at kthcorp dot com@iolothebard
64