H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수

1,100 views
971 views

Published on

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,100
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
25
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide

H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수

  1. 1. (푸딩얼굴인식
  2. 2.  앱을
  3. 3.  통해서
  4. 4.  본)하이브리드앱아키텍쳐
  5. 5.  및
  6. 6.  개발
  7. 7.  사례앱스프레소팀
  8. 8.  |
  9. 9.  장동수 1
  10. 10. index1. 하이브리드앱
  11. 11.  아키텍쳐
  12. 12.  개요2. 하이브드리앱
  13. 13.  유형
  14. 14.  및
  15. 15.  특징3. 푸딩얼굴인식
  16. 16.  앱
  17. 17.  개발
  18. 18.  사례
  19. 19.  공유4. 앱스프레소
  20. 20.  플러그인
  21. 21.  활용5. Lessons
  22. 22.  Learned6. References 2
  23. 23. 이런
  24. 24.  거...아님
  25. 25.  -_-; 하이브리드앱
  26. 26.  아키텍쳐
  27. 27.  개요 3
  28. 28. 하이브리드앱
  29. 29.  아키텍쳐
  30. 30.  구성
  31. 31.  요소 네이티브 하이브리드 웹 UI
  32. 32.  툴킷 웹
  33. 33.  UI
  34. 34.  툴킷 자바스크립트
  35. 35.  프레임웍/라이브러리 웹
  36. 36.  표준
  37. 37.  기술 프레임웍 HTML5 CSS 자바스크립트네이티브
  38. 38.  라이브러리 비표준
  39. 39.  Device
  40. 40.  APIs 표준
  41. 41.  Device
  42. 42.  APIs 개발
  43. 43.  도구 웹브라우져
  44. 44.  “엔진” 웹브라우져
  45. 45.  “앱” 플랫폼
  46. 46.  SDK 안드로이드
  47. 47.   iOS
  48. 48.  SDK 윈폰7
  49. 49.  SDK …⋯ SDK 4
  50. 50. 하이브리드앱의
  51. 51.  꿈Application
  52. 52.  Quality BEST 네이티브 하이브리드 웹 WORST Development
  53. 53.  Cost 5
  54. 54. 하이브리드라는
  55. 55.  이름의
  56. 56.  “짬뽕”... 난, 물~
  57. 57.  H2O~ OH H 6
  58. 58.
  59. 59.  개발자들을
  60. 60.  유혹하는
  61. 61.  “파란”
  62. 62.  짬뽕~ 내가, 하이브리드~ 네이티브웹 웹 7
  63. 63.
  64. 64.  개발자들을
  65. 65.  유혹하는
  66. 66.  “빨간”
  67. 67.  짬뽕~ 나도, 하이브리드~ 웹네이티브 네이티브 8
  68. 68. 네이티브와
  69. 69.  웹의
  70. 70.  결합 Flash/Flex? Active-X?Java
  71. 71.  Applet? 문제는.... 다리!! Native-Web네이티브 Bridge 웹 9
  72. 72. 네이티브와
  73. 73.  웹의
  74. 74.  결합 WebViewWebViewClient
  75. 75.  
  76. 76.  WebChromeClient loadUrl addJavascriptInterface UIWebView UIWebViewDelegate loadRequest stringByEvaluatingJavascriptFromString 10
  77. 77. 네이티브와
  78. 78.  웹의
  79. 79.  결합 자바스크립트 캐시 그래봤자, 문자열~ URL 쿠키어차피,꼼수 그리고... HTTP! 그림 출처: http://petticoatsandpistols.com/2010/05/12/ 11
  80. 80. 하이브리드앱
  81. 81.  유형
  82. 82.  및
  83. 83.  특징 12
  84. 84. 네이티브
  85. 85.  지향
  86. 86.  하이브리드앱 사실상
  87. 87.  네이티브, 웹은
  88. 88.  거들
  89. 89.  뿐...· 제한적이고
  90. 90.  직관적인
  91. 91.  네이티브와
  92. 92.  웹의
  93. 93.  결합· 웹브라우져
  94. 94.  as-a
  95. 95.  UI
  96. 96.  컴포넌트· 도움말,
  97. 97.  앱/개발사
  98. 98.  소개,
  99. 99.  공지사항/새소식...· 웹
  100. 100.  기반
  101. 101.  사용자
  102. 102.  인증(OAuth)... 13
  103. 103. 웹브라우져
  104. 104.  as-a
  105. 105.  UI
  106. 106.  컴포넌트 14
  107. 107.
  108. 108.  기반
  109. 109.  사용자
  110. 110.  인증 15
  111. 111. 예제
  112. 112.  코드(안드로이드)웹서버 컨텐츠 불러오기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
  113. 113. 예제
  114. 114.  코드(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
  115. 115. “한
  116. 116.  지붕
  117. 117.  두
  118. 118.  가족”
  119. 119.  하이브리드앱웹은
  120. 120.  아니지만
  121. 121.  네이티브도
  122. 122.  아닌, 그러나
  123. 123.  웹스러운... · 광범위하고
  124. 124.  일관성없는
  125. 125.  네이티브와
  126. 126.  웹의
  127. 127.  결합 · 웹브라우져를
  128. 128.  내장한
  129. 129.  네이티브
  130. 130.  클라이언트 · 기존
  131. 131.  웹
  132. 132.  서버
  133. 133.  “조금
  134. 134.  손
  135. 135.  봐서...”
  136. 136.  재활용 · 기존
  137. 137.  웹
  138. 138.  컨텐츠
  139. 139.  “조금
  140. 140.  손
  141. 141.  봐서...”
  142. 142.  재활용 18
  143. 143. 여기도
  144. 144.  하이브리드~ 19
  145. 145. 저기도
  146. 146.  하이브리드~ 20
  147. 147. 예제
  148. 148.  코드(안드로이드)링크 클릭 가로채기...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
  149. 149. 예제
  150. 150.  코드(iOS)링크 클릭 가로채기@interface NoticeViewController : UIViewControllerUIWebViewDelegate, 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
  151. 151. 예제
  152. 152.  코드(안드로이드)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;}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; }});... 23
  153. 153. 예제
  154. 154.  코드(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
  155. 155.
  156. 156.  지향
  157. 157.  하이브리드앱 사실상
  158. 158.  웹, 네이티브는
  159. 159.  거들
  160. 160.  뿐...· 광범위하지만
  161. 161.  일관성있는
  162. 162.  네이티브와
  163. 163.  웹의
  164. 164.  결합
  165. 165.  · 클라이언트
  166. 166.  사이드
  167. 167.  “웹앱”· 기존
  168. 168.  웹
  169. 169.  서버
  170. 170.  +
  171. 171.  RESTful
  172. 172.  API
  173. 173.  서버· 기본적인
  174. 174.  웹
  175. 175.  컨텐츠는
  176. 176.  앱에
  177. 177.  포함 25
  178. 178. 하이브리드
  179. 179.  모바일
  180. 180.  앱
  181. 181.  프레임웍 26
  182. 182. 이것도
  183. 183.  하이브리드! 27
  184. 184. 푸딩얼굴인식앱
  185. 185.  개발
  186. 186.  사례
  187. 187.  공유 28
  188. 188. “푸딩얼굴인식
  189. 189.  앱”
  190. 190.  소개
  191. 191.  
  192. 192.  
  193. 193.  
  194. 194.  
  195. 195.  
  196. 196.  
  197. 197.  
  198. 198.  푸딩얼굴인식
  199. 199.  앱은...· 600만+
  200. 200.  다운로드!· @iolothebard
  201. 201.  
  202. 202.  
  203. 203.  
  204. 204.  
  205. 205.  
  206. 206.  와
  207. 207.  @seti222· 5500+
  208. 208.  줄의
  209. 209.  자바스립트· 2700+
  210. 210.  줄의
  211. 211.  CSS· 200+
  212. 212.  줄의
  213. 213.  네이티브
  214. 214.  코드· 앱스프레소
  215. 215.  0.9+
  216. 216.  내부
  217. 217.  개발
  218. 218.  버전 29
  219. 219. 단일
  220. 220.  페이지
  221. 221.  인터페이스 index.html #pageId pageId.css pageId.js Active Pageshow/hide 30
  222. 222. 단일
  223. 223.  페이지
  224. 224.  인터페이스 웹앱도
  225. 225.  MVC가
  226. 226.  필요해! 자바스크립트가
  227. 227.  컨트롤러!· 웹
  228. 228.  서버도
  229. 229.  없는
  230. 230.  데...
  231. 231.  페이지
  232. 232.  이동은
  233. 233.  왜?!· (GUI)
  234. 234.  애플리케이션
  235. 235.  스타일의
  236. 236.  “상태”
  237. 237.  관리· 빠른
  238. 238.  화면
  239. 239.  전환
  240. 240.  
  241. 241.  화면
  242. 242.  전환
  243. 243.  효과· 체감
  244. 244.  성능
  245. 245.  UP!· 메모리
  246. 246.  사용량
  247. 247.  UP!· 그런데...
  248. 248.  공동
  249. 249.  작업은
  250. 250.  어떻게?
  251. 251.  -_-; 31
  252. 252. 단일
  253. 253.  페이지
  254. 254.  인터페이스HTML마크업: section#faceFoundPagesection 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 h1span data-nls=”faceFoundPage.title”얼굴인식 결과/span/h1 /header article CSS스타일:faceFoundPage.css ... #faceFoundPage { /article background-color:rgb(255,255,255); footer } ... #faceFoundPage article, #faceFoundPage footer { /footer background-color:rgb(138,135,136);/section } ...자바스크립트:faceFoundPage.jsvar 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
  255. 255. “Placeholder”
  256. 256.  HTML
  257. 257.  Markup 자바스크립트 API
  258. 258.  서버 웹
  259. 259.  서버 HTML 로컬 캐시 스토리지 캐시 앱Static
  260. 260.  HTML
  261. 261.  Fragment Placeholder Data Template + 이름 클래스 레벨 ★ ♥ ♦ 이름 클래스 레벨 iolo bard 만렙 iolo bard 만렙 foreach 장동수 개발자 쪼렙 장동수 개발자 쪼렙 ★ ♥ ♦ ... ... ... ... ... ... end 33
  262. 262. “Placeholder”
  263. 263.  HTML
  264. 264.  Markup 웹앱도
  265. 265.  MVC가
  266. 266.  필요해! 뷰와
  267. 267.  모델의
  268. 268.  분리· HTML
  269. 269.  마크업을
  270. 270.  위한
  271. 271.  “변수”· 웹
  272. 272.  서버
  273. 273.  개발에서
  274. 274.  
  275. 275.  클라이언트에
  276. 276.  적용 smarty,
  277. 277.  ...)을
  278. 278.  웹 널리
  279. 279.  쓰이는
  280. 280.  템플릿(velocity,
  281. 281.  · 서버
  282. 282.  부하는
  283. 283.  DOWN!· 데이터
  284. 284.  전송량도
  285. 285.  DOWN!· 캐시
  286. 286.  히트율은
  287. 287.  UP! 34
  288. 288. “Placeholder”
  289. 289.  HTML
  290. 290.  Markupdiv id=”faceFound_starTemplate” class=”jj-template” div class=”face” data-fastclick=”true”/div div class=”rank”/div p class=”rate”span class=”rateText”/spansmall%/small/p p class=”nameText” data-fastclick=”true”/p p class=”info”/p p class=”descriptionText”/p/divdiv 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
  291. 291. “Marker”
  292. 292.  CSS
  293. 293.  Class 웹앱도
  294. 294.  MVC가
  295. 295.  필요해! 뷰와
  296. 296.  컨트롤러의
  297. 297.  분리· CSS
  298. 298.  스타일시트를
  299. 299.  위한
  300. 300.  “조건문”· 프론트엔드
  301. 301.  UI
  302. 302.  개발자와
  303. 303.  자바스크립트
  304. 304.  개발자의
  305. 305.  약속· 화면의
  306. 306.  동적인
  307. 307.  변화를
  308. 308.  자바스크립트없이
  309. 309.  확인
  310. 310.  
  311. 311.  제어· HTML/CSS는
  312. 312.  “쬐끔”
  313. 313.  복잡해지고...
  314. 314.  -_-;· 자바스크립트는
  315. 315.  “쬐끔”
  316. 316.  단순해지고...
  317. 317.  -_-;· 함께
  318. 318.  일하기는
  319. 319.  더
  320. 320.  좋아지고~
  321. 321.  ^O^ 36
  322. 322. “Marker”
  323. 323.  CSS
  324. 324.  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
  325. 325. 단말
  326. 326.  해상도별
  327. 327.  최적화 Responsive
  328. 328.  Web
  329. 329.  Design?지금은
  330. 330.  곤란하니,
  331. 331.  기다려
  332. 332.  달라~· HTML
  333. 333.  태그에
  334. 334.  마커
  335. 335.  CSS
  336. 336.  클래스
  337. 337.  추가/활용· 기본적으로
  338. 338.  해상도에
  339. 339.  자유로운
  340. 340.  프론트엔드
  341. 341.  UI
  342. 342.  개발· 널리
  343. 343.  쓰이는
  344. 344.  해상도는
  345. 345.  꼼꼼하게
  346. 346.  “미세”
  347. 347.  조정· 특이한
  348. 348.  해상도는
  349. 349.  최소한의
  350. 350.  “미세”
  351. 351.  조정· 이
  352. 352.  한
  353. 353.  몸
  354. 354.  희생해서...
  355. 355.  모두가
  356. 356.  행복할
  357. 357.  수
  358. 358.  있다면...
  359. 359.  ㅠㅠ 38
  360. 360. “Marker”
  361. 361.  CSS
  362. 362.  Class단말 플랫폼/해상도 마커 클래스 초기화 스크립트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’);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; }... 39
  363. 363. 다국어
  364. 364.  처리 I18N?
  365. 365.  L10N?
  366. 366.  A11Y? 이제는
  367. 367.  선택이
  368. 368.  아닌
  369. 369.  필수!· HTML
  370. 370.  태그에
  371. 371.  마커
  372. 372.  CSS
  373. 373.  클래스
  374. 374.  추가/활용· “그림
  375. 375.  글자”
  376. 376.  사용
  377. 377.  최소화
  378. 378.  :
  379. 379.  국제화
  380. 380.  
  381. 381.  접근성· 일관성있는
  382. 382.  번역어
  383. 383.  식별자· 언어별
  384. 384.  어순
  385. 385.  /
  386. 386.  길이
  387. 387.  /
  388. 388.  너비
  389. 389.  차이
  390. 390.  고려· 언어별
  391. 391.  UI
  392. 392.  “미세”
  393. 393.  조정 40
  394. 394. 다국어
  395. 395.  처리 41
  396. 396. 다국어
  397. 397.  처리다국어 지원 초기화 스크립트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”, ...다국어 지원 자리 잡기 태그buttonspan data-nls=”common.back”이전/span/button...h1span data-nls=”setupTwitterPage.title”설정 - 트위터/span/h1...다국어 번역 텍스트 치환 결과buttonspan data-nls=”common.back”Back/span/button...h1span 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
  398. 398. Ant를
  399. 399.  이용한
  400. 400.  빌드
  401. 401.  자동화1.
  402. 402.  verify:
  403. 403.  jslint2.
  404. 404.  merge:
  405. 405.  ant
  406. 406.  concat3.
  407. 407.  compress:
  408. 408.  YUICompressor4.
  409. 409.  preprocess:
  410. 410.  ant
  411. 411.  filter5.
  412. 412.  test:
  413. 413.  JSTestDriver6.
  414. 414.  docs:
  415. 415.  JSDocToolkit(v2) 43
  416. 416. Ant를
  417. 417.  이용한
  418. 418.  빌드
  419. 419.  자동화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/targettarget 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=”#xFEFF;”/ /filterchain /concat echo message=”merged into ${js.out.merged}”//targettarget 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
  420. 420. Ant를
  421. 421.  이용한
  422. 422.  빌드
  423. 423.  자동화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/targettarget 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
  424. 424. 네이티브와의
  425. 425.  결합 사용한
  426. 426.  앱스프레소
  427. 427.  플러그인· deviceapis.filesystem:
  428. 428.  파일
  429. 429.  입출력· deviceapis.deviceinteraction:
  430. 430.  화면
  431. 431.  꺼짐
  432. 432.  방지
  433. 433.  /
  434. 434.  진동· ax.ext.media:
  435. 435.  카메라
  436. 436.  /
  437. 437.  포토
  438. 438.  앨범
  439. 439.  /
  440. 440.  효과음· ax.ext.net:
  441. 441.  업로드
  442. 442.  /
  443. 443.  다운로드· ax.ext.ui:
  444. 444.  네이티브
  445. 445.  UI
  446. 446.  /
  447. 447.  차일드
  448. 448.  브라우져· ax.ext.ga:
  449. 449.  구글
  450. 450.  통계· ax.ext.admob:
  451. 451.  애드몹
  452. 452.  광고· kth.puddingface:
  453. 453.  푸딩얼굴인식앱
  454. 454.  전용
  455. 455.  *^^* 46
  456. 456. 앱스프레소
  457. 457.  플러그인
  458. 458.  활용 47
  459. 459.  앱스프레소
  460. 460.  플러그인
  461. 461.  구조 Appspresso
  462. 462.  Plugin
  463. 463.   Appspresso
  464. 464.  Appspresso
  465. 465.   Project Plugin
  466. 466.  Application
  467. 467.   Archive
  468. 468.   Project axplugin.xml axplugin.js (*.axp) res overlay link export
  469. 469.  run lib*.a *.jar
  470. 470.  share iOS
  471. 471.  Native
  472. 472.  Module Android
  473. 473.  Native
  474. 474.  Module (Xcode
  475. 475.  Static
  476. 476.  Library
  477. 477.  Project) (Android
  478. 478.  Library
  479. 479.  Project) 48
  480. 480. 앱스프레소
  481. 481.  플러그인
  482. 482.  API AxPlugin AxPluginContext int
  483. 483.  getId()/*
  484. 484.  YOUR
  485. 485.  CODE
  486. 486.  HERE
  487. 487.  */ 2 String
  488. 488.  getMethod() activate(AxRuntimeContext) Object[]
  489. 489.  getParams()deactivate(AxRuntimeContext) ... execute(AxPluginContext) sendResult([result]) ... sendError(code[,
  490. 490.  message]) 2 4 AxRuntimeContext 1 자바스크립트
  491. 491.  API getWebView() getWidget() ... requirePlugin(pluginId) 플랫폼
  492. 492.  확장
  493. 493.  API executeJavaScript(script) ... Android iOS 49
  494. 494. 앱스프레소
  495. 495.  플러그인
  496. 496.  플랫폼
  497. 497.  확장
  498. 498.  API·안드로이드
  499. 499.  전용 · Activity · ActivityListener · WebViewListener · WebViewClientListener/WebChromeClientListener· iOS
  500. 500.  전용 · UIViewController · UIApplicationDelegate · UIWebViewDelegate · AxViewControllerDelegate 50
  501. 501. 앱스프레소
  502. 502.  플러그인
  503. 503.  자바스크립트
  504. 504.  API·AxPlugin의
  505. 505.  자바스크립트
  506. 506.  “stub” · function
  507. 507.  execSync(method,
  508. 508.  params) · function
  509. 509.  execAsync(method,
  510. 510.  successCallback,
  511. 511.   errorCallback,
  512. 512.  params) · params:
  513. 513.  array
  514. 514.  of
  515. 515.  arguments · successCallback:
  516. 516.  function(result)
  517. 517.  {
  518. 518.  ...
  519. 519.  } · errorCallback:
  520. 520.  function(error)
  521. 521.  {
  522. 522.  ...
  523. 523.  }· ax,
  524. 524.  ax.error,
  525. 525.  ax.util,
  526. 526.  ax.console,
  527. 527.   ax.request,
  528. 528.  ax.bridge,
  529. 529.  ax.plugin,
  530. 530.  ... 51
  531. 531. 앱스프레소
  532. 532.  플러그인
  533. 533.  개발
  534. 534.  실습 네이티브
  535. 535.  주소록
  536. 536.  UI
  537. 537.  플러그인· deviceapis.pim.contact
  538. 538.  는...· 너무
  539. 539.  어려워
  540. 540.  +
  541. 541.  너무
  542. 542.  느려
  543. 543.  +
  544. 544.  뽀대도
  545. 545.  안나...
  546. 546.  
  547. 547.  
  548. 548.  
  549. 549.  
  550. 550.  
  551. 551.  
  552. 552.  
  553. 553.  
  554. 554.  
  555. 555.  x3· 그래서,
  556. 556.  @bluenmad
  557. 557.  
  558. 558.  
  559. 559.  
  560. 560.  
  561. 561.  
  562. 562.  
  563. 563.  가
  564. 564.  만들었습니다~ 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
  565. 565. axplugin.xml?xml version=”1.0” encoding=”UTF-8”?axplugin id=”ax.ext.contact” version=”1.0”! descriptionContact Extension API Appspresso Plugin! /description! urlhttp://appspresso.com/url! authorAppspresso Dev. Team/author! licenseCopyright (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
  566. 566. axplugin.js/*jslint browser:true, confusion:true, /**debug:true, devel:true, nomen:true, * pick a contactplusplus:true, vars:true */ */** * @param {function} callback * @fileOverview Contact Extension API * @param {function} errback * @author blueNmad * @param {ax.ext.contact.ContactOpts} opts * @version 1.0 * @return AxRequest */ * @methodOf ax.ext.contact(function () { */ “use strict”; function pickContact(callback, errback, var NS_CONTACT = “ax.ext.contact”; opts) { var PREFIX_CONTACT = “ax.ext.contact”; onPickContactCallback = callback; /** return this.execAsync(‘pickContact’, * Contact Extension API ax.nop, errback, [opts || {}]); * } * @namespace * @name ax.ext.contact function onPickContact(contact) { */ if ( !! onPickContactCallback) { onPickContactCallback(eval(contact)); /** } * @class * @name ContactOpts onPickContactCallback = undefined; * @memberOf ax.ext.contact } */ ax.plugin(PREFIX_CONTACT, { /** ‘pickContact’: pickContact, * native callback for pickContact. ‘onPickContact’: onPickContact * }, NS_CONTACT); * @param result })(); * @memberOf ax.ext.contact * @private */ var onPickContactCallback = undefined; 54
  567. 567. com...contact.ContactPlugin.javapackage com.appspresso.screw.contact; return super.onActivityResult(activity, requestCode, resultCode, data);import android.app.Activity; }import android.content.Intent; };import android.provider.ContactsContract; public void activate(import com.appspresso.api.AxPluginContext; AxRuntimeContext runtimeContext) {import com.appspresso.api.AxRuntimeContext; super.activate(runtimeContext);import com.appspresso.api.DefaultAxPlugin;... runtimeContext.addActivityListener(중간생략 activityListener);... }/** public void deactivate( * Appspresso Plugin Android Module AxRuntimeContext runtimeContext) { * runtimeContext.removeActivityListener( * id: ax.ext.contact activityListener); * version: 1.0.0 * super.deactivate(runtimeContext); */ }public class ContactPlugin extendsDefaultAxPlugin { public void pickContact( private static final int AxPluginContext context) { REQ_PICK_CONTACT = 62000; Intent intent = new Intent( Intent.ACTION_PICK, private ActivityListener activityListener = ContactsContract.Contacts.CONTENT_URI); new ActivityAdapter() { runtimeContext.getActivity() public boolean onActivityResult( .startActivityForResult(intent, Activity activity, int requestCode, int REQ_PICK_CONTACT); resultCode, Intent data) { context.sendResult(); if (ContactPlugin.REQ_PICK_CONTACT == } requestCode data != null) { } return ContactUtils.onPickContact( runtimeContext, data); } 55

×