캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012

7,835
-1

Published on

0 Comments
17 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
7,835
On Slideshare
0
From Embeds
0
Number of Embeds
10
Actions
Shares
0
Downloads
137
Comments
0
Likes
17
Embeds 0
No embeds

No notes for slide

캠프앱 개발 사례를 통해 본 하이브리드앱 어디까지 | Devon 2012

  1. 1. 캠프앱 개발 사례를 통해 본하이브리드앱 어디까지 다음 커뮤니케이션 유승근
  2. 2. 오늘 얘기할 순서 ...• 개발 전 - Cross Platform - 캠프 하이브리드• 개발 중 - 화면 영역별 구현 방법 - WebView & Web-App Bridge - Application Cache - 버전 관리
  3. 3. 오늘 얘기할 순서• 오픈 후(최적화) - WebView Request Intercept - JS 크기 줄이기 - Touch 이벤트• 마무리하며
  4. 4. Write Once, Run Anywhere
  5. 5. Cross Platform Tools • JavaScript Libraries : - jQuery Mobile, Sencha Touch • Wrappers around web applications : - Adobe PhoneGap, KTH Appspresso • Transformers to Native code : - Appcelerator Titanium • Adobe AirCross-Platform Tools: Build Once and Run Everywhere
  6. 6. 캠프 하이브리드앱 Web App
  7. 7. 캠프 하이브리드앱 Web App Native
  8. 8. 캠프 하이브리드앱 Web App Hybrid Native
  9. 9. 캠프 하이브리드앱• 기대 • 우려- One Source Multi Use - 시스템 복잡도 증가- 상대적으로 업데이트 용이 - 웹과 앱의 배포 시점 동기화- Web UI 구현 문제 해결 - WebView 성능 문제
  10. 10. 개발 시작!
  11. 11. Native / Web무엇으로 구현해야 하나?
  12. 12. Native 영역• 레이아웃 - 네비게이션바, 메뉴 - position: fixed• 사용자 입력 Form - 이미지 업로드 - 날짜 입력 등 복잡한 입력 요소들• 다음 공통 컴포넌트 - 로그인, 지도, 이미지뷰어
  13. 13. Native 영역• 레이아웃 - 네비게이션바, 메뉴 - position: fixed• 사용자 입력 Form - 이미지 업로드 - 날짜 입력 등 복잡한 입력 요소들• 다음 공통 컴포넌트 - 로그인, 지도, 이미지뷰어
  14. 14. Native 영역• 레이아웃 - 네비게이션바, 메뉴 - position: fixed• 사용자 입력 Form - 이미지 업로드 - 날짜 입력 등 복잡한 입력 요소들• 다음 공통 컴포넌트 - 로그인, 지도, 이미지뷰어
  15. 15. Native 영역• 레이아웃 - 네비게이션바, 메뉴 - position: fixed• 사용자 입력 Form - 이미지 업로드 - 날짜 입력 등 복잡한 입력 요소들• 다음 공통 컴포넌트 - 로그인, 지도, 이미지뷰어
  16. 16. Native 영역• 레이아웃 - 네비게이션바, 메뉴 - position: fixed• 사용자 입력 Form - 이미지 업로드 - 날짜 입력 등 복잡한 입력 요소들• 다음 공통 컴포넌트 - 로그인, 지도, 이미지뷰어
  17. 17. input Focus
  18. 18. input Focus iOS 6 webview.keyboardDisplayRequiresUserAction = NO;
  19. 19. WebView
  20. 20. WebView• Android - WebView - WebSettings - WebChromeClient - WebViewClient• iOS - UIWebView - UIWebViewDelegate
  21. 21. Browser WebView Android 4.0.4
  22. 22. Safari UIWebView iOS 6
  23. 23. Native to Web Call• Android - webview.loadUrl("http://camp.daum.net") - webview.loadUrl("javascript:hello(world)")• iOS - [webview loadRequest:] - [webview stringByEvaluatingJavaScriptFromString:]
  24. 24. Web to Native Call• location.href - WebViewClient.shouldOverrideUrlLoading() - webView:shouldStartLoadWithRequest:navigationType:• window.prompt - Android에서만 사용 - WebChromeClient.onJsPrompt()• Android - webview.addJavaScriptInterface() - Java 객체를 JavaScript 객체로 삽입하여 public method 호출 가능 - iOS, Android 동일 인터페이스로 제공 위해 미사용
  25. 25. Internal URL Scheme• Web to Native 명령 규칙scheme://namespace/action/param1/...ex) daumcamp://daum.camp/camp/10
  26. 26. location.href• URL 변경을 연속해서 발생시키면 마지막 하나만 호출• 해결책 - setTimeout : 호출 시점 보장 못함 - iframe function callByIframe(scheme) { var iframe = document.createElement(iframe); iframe.src = scheme; document.body.appendChild(iframe);     setTimeout(function() {         document.body.removeChild(iframe);     }, 100); };
  27. 27. Web to Native Callback • Native 호출 후 응답이 필요한 경우location.href = daumcamp://daum.camp/post/postComplete; callback JS 함수
  28. 28. Cookie• Android - CookieManager - CookieSyncManager• iOS - NSHTTPCookie - NSHTTPCookieStorage• Cookie 관리자는 Singleton 객체
  29. 29. Application Cache
  30. 30. Application Cache <html manifest="appcache.manifest"> ... <script> applicationCache.addEventListener(updateready, function(e) { applicationCache.swapCache(); window.reload(); } ); </script> ... </html>
  31. 31. Application Cache Manifest CACHE MANIFEST # 2012-06-18:v3 CACHE: index.html css/style.css FALLBACK: / /offline.html NETWORK: *
  32. 32. Application Cache - Eventschecking downloading progress... cached 최초실행checking noupdate 변경없음checking downloading progress... updateready 앱캐쉬 업데이트checking obsolete 앱캐쉬 제거checking ••• error 에러
  33. 33. checking
  34. 34. checking downloading
  35. 35. checking downloading progress...
  36. 36. checking downloading progress... updatereadyappCache.swapCache();window.reload();
  37. 37. checking downloading progress... updateready
  38. 38. 이미지 해상도 대응 MDPI HDPI
  39. 39. 이미지 해상도 대응• Cookie를 이용한 해결책 - Native에서 해상도 정보 취득 - 로딩 전 WebView에 해상도 정보를 cookie로 굽기 - manifest 파일은 cookie 값을 바탕으로 동적 생성
  40. 40. 이미지 해상도 대응float density = Context.getResources() .getDisplayMetrics().density;String pixelRatio = "1";if (density >= 2.0) { pixelRatio = "2";} else if (density >= 1.5) { pixelRatio = "1.5";}// setCookie pixelRatio
  41. 41. 이미지 해상도 대응BOOL ios4 = [[UIScreen mainScreen] respondsToSelector: @selector(displayLinkWithTarget:selector:)];String* pixelRatio = @"1"; iOS 4 이상인지 확인if (ios4 && [UIScreen mainScreen].scale == 2.0) { // Retina display pixelRatio = @"2";}// setCookie pixelRatio
  42. 42. 이미지 해상도 대응#set ($IMG_TYPE = "480") Velocity Template#if ($PIXEL_RATIO == "1") #set ($IMG_TYPE = "320")#endif#set ($PATH = "http://m1.daumcdn.net/m/${IMG_TYPE}/")...CACHE:$!{PATH}/ico_camp01.png$!{PATH}/ico_camp02.png
  43. 43. 주의 사항• html 파일은 항상 캐쉬• 업데이트는 리소스 파일이 아닌 Manifest 파일의 변경• Application cache 제거시 http 응답은 404 - obsolete 이벤트 처리 필요• Android 2.1은 Network 섹션에서 * 선택자 미지원 - 명시되지 않은 네트워크 요청은 모두 취소
  44. 44. 버전 관리
  45. 45. 1.0.0 App Web 1.0.0
  46. 46. 1.0.0 App Web 1.0.0 Web 1.0.1
  47. 47. 1.0.0 App Web 1.0.0 Web 1.0.1 Web 1.0.2
  48. 48. 1.0.0 App Web 1.0.0 Web 1.0.1 Web 1.0.21.0.1 App Web 1.0.3
  49. 49. 1.0.0 App Web 1.0.0 Web 1.0.1 Web 1.0.21.0.1 App Web 1.0.3 Web 1.0.4
  50. 50. 1.0.0 App Web 1.0.0 Web 1.0.1 Mapping Web 1.0.2 Table1.0.1 App Web 1.0.3 Web 1.0.4
  51. 51. Version Mapping Table <bean ... > " <property name="version" value="1.0.10-p6"/> " <property name="androidVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.4)">1.0.6</prop> " " " <prop key="[1.0.4,)">1.0.10-p6</prop> " " </props> " </property> " <property name="iosVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.2)">1.0.9-p3</prop> " " " <prop key="[1.0.2,)">1.0.10-p6</prop> " " </props> " </property> </bean>
  52. 52. Version Mapping Table <bean ... > " <property name="version" value="1.0.10-p6"/> 최신 웹 버전 " <property name="androidVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.4)">1.0.6</prop> " " " <prop key="[1.0.4,)">1.0.10-p6</prop> " " </props> " </property> " <property name="iosVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.2)">1.0.9-p3</prop> " " " <prop key="[1.0.2,)">1.0.10-p6</prop> " " </props> " </property> </bean>
  53. 53. Version Mapping Table <bean ... > " <property name="version" value="1.0.10-p6"/> " <property name="androidVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.4)">1.0.6</prop> Android Version " " " <prop key="[1.0.4,)">1.0.10-p6</prop> Mapping " " </props> " </property> " <property name="iosVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.2)">1.0.9-p3</prop> " " " <prop key="[1.0.2,)">1.0.10-p6</prop> " " </props> " </property> </bean>
  54. 54. Version Mapping Table <bean ... > " <property name="version" value="1.0.10-p6"/> " <property name="androidVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.4)">1.0.6</prop> " " " <prop key="[1.0.4,)">1.0.10-p6</prop> " " </props> " </property> " <property name="iosVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.2)">1.0.9-p3</prop> iPhone Version " " " <prop key="[1.0.2,)">1.0.10-p6</prop> Mapping " " </props> " </property> </bean>
  55. 55. Version Mapping Table <bean ... > " <property name="version" value="1.0.10-p6"/> App Version Range Web Version " <property name="androidVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.4)">1.0.6</prop> " " " <prop key="[1.0.4,)">1.0.10-p6</prop> " " </props> " </property> " <property name="iosVersionMap"> " " <props> " " " <prop key="[1.0.0,1.0.2)">1.0.9-p3</prop> " " " <prop key="[1.0.2,)">1.0.10-p6</prop> " " </props> " </property> </bean>
  56. 56. App to Web URLhttp://camp.daum.net/app/{platform}/{version}/...ex) http://camp.daum.net/app/ios/1.0.2/...
  57. 57. 고민거리• 현재 JS/CSS 만 버전 관리 대상• HTML, Manifest 파일도 버전관리 해야 하지 않을까?
  58. 58. 오픈 후...
  59. 59. SunSpider JavaScript Benchmark
  60. 60. Android Browser WebView 7,000 5,250running time(ms) 3,500 1,750 0 Android 2.3 Android 4.0
  61. 61. iOS Safari WebView 12,000 9,000running time(ms) 6,000 3,000 0 iOS 4.3 iOS 5 iOS 6
  62. 62. 장인은 도구를 탓하지 않는다.
  63. 63. Web Application 최적화
  64. 64. 로딩 과정 onDOMContentLoadedLoad start onLoad onHashchange css, js load/parse/eval init() router / data load / render Boot View
  65. 65. 로딩 과정 onDOMContentLoadedLoad start onLoad onHashchange css, js load/parse/eval init() router / data load / render App에서는 지속적 발생 Boot View
  66. 66. 로딩 중 구멍 찾기
  67. 67. Load Start CSS application cache 웹앱 실행시 필수 정보그러나 앱에도 존재하는 정보 <script src="/env"> no-cache baselib.js camp.js application cache init() View Render
  68. 68. Script Blocking Script Loading
  69. 69. Script Blocking Script Loading API call
  70. 70. Script Blocking Script Loading API call Complete
  71. 71. Script Blocking Script Loading
  72. 72. WebScript Blocking Load Start CSS app cache <script no-cache src="/env"> baselib.js app cache camp.js init() View Render
  73. 73. WebScript Blocking Load Start App CSS app cache Intercept <script no-cache src="/env"> Http Response baselib.js App에 있는 정보 바탕으로 camp.js app cache 응답 생성 init() View Render
  74. 74. WebView Request Intercept• Android - webViewClient.shouldInterceptRequest() - API Level 11 - Android 3.0 이상 지원• iOS - NSURLCache class의 cachedResponseForRequest: method override - [NSURLCache setSharedURLCache:]
  75. 75. JS 코드 줄이기
  76. 76. JS 코드 줄이기• Google Closure Compiler - Minify & Code Merging• SIMPLE_OPTIMIZATIONS - 공백, 주석 삭제, Local 변수를 짧은 변수명으로 변경 - 도달하지 않는 코드 제거 / 미사용 변수 제거
  77. 77. JS 코드 줄이기 var isDev = false; function hello(name) { if (isDev) { console.log(hello called); } return Hello, + name; } hello(New user);
  78. 78. JS 코드 줄이기 var isDev = false; function hello(name) { if (isDev) { console.log(hello called); } return Hello, + name; } hello(New user); SIMPLE_OPTIMIZATION var isDev=!1;function hello(a) {isDev&&console.log("hello called");return"Hello, "+a} hello("New user");
  79. 79. JS 코드 줄이기 var isDev = false; function hello(name) { if (false) { console.log(hello called); } return Hello, + name; } hello(New user);
  80. 80. JS 코드 줄이기 var isDev = false; function hello(name) { if (false) { console.log(hello called); } return Hello, + name; } hello(New user); SIMPLE_OPTIMIZATION function hello(a){return"Hello, "+a}hello("New user");
  81. 81. JS Pre-Processor• Idea - 플랫폼 구분자를 상수로 치환하여 컴파일러가 제거 가능하게 하자 - 플랫폼 별로 별도의 JavaScript 파일 생성• JavaScript Parser - Esprima : Source Code > Abstract Syntax Tree - Escodegen : Abstract Syntax Tree > Source Code
  82. 82. Source Code if (camp.isApp) { " camp.Util.installExtAnchorHandler(); " if (camp.os === camp.OS.IOS) { " " camp.UI.navi.init(); " " camp.Util.hideAddressBar(); " } } else { " // 메뉴, 네비 적용 " camp.UI.navi.init(); " camp.UI.menu.init(); " // body의 높이를 window.height에 맞춤 " camp.Util.hideAddressBar(); " // daum id 저장 " camp.Env.uid = camp.Util.getUserId(); " // 로그인 인증 쿠키 갱신 타이머 시작 " camp.Function.startLoginCheckTimer(); }
  83. 83. Pre-processed Code if (true) { 아이폰 앱인 경우 camp.Util.installExtAnchorHandler(); if (ios === ios) { camp.UI.navi.init(); camp.Util.hideAddressBar(); } } else { camp.UI.navi.init(); camp.UI.menu.init(); camp.Util.hideAddressBar(); camp.Env.uid = camp.Util.getUserId(); camp.Function.startLoginCheckTimer(); }
  84. 84. Compiled Code• 전처리기 처리후 컴파일된 코드 (83 byte) camp.Util.installExtAnchorHandler();camp.UI.navi.init( );camp.Util.hideAddressBar();• 원 소스 코드로 컴파일된 코드 (262 byte) camp.isApp? (camp.Util.installExtAnchorHandler(),camp.os===camp.OS .IOS&&(camp.UI.navi.init(),camp.Util.hideAddressBar()) ): (camp.UI.navi.init(),camp.UI.menu.init(),camp.Util.hid eAddressBar(),camp.Env.uid=camp.Util.getUserId(),camp. Function.startLoginCheckTimer());
  85. 85. JavaScript Build Process MW-bin- MW-JS JS AND-bin-org-JS pre-processor AND-JS google-closure JS IOS-JS IOS-bin-JS
  86. 86. JS Pre-Processor• Esprima에 없는 것들 - Comment Parser - Semantic Analysis
  87. 87. Touch
  88. 88. Single Touch ≒ Click touchstart Touch Events touchend mouseover mousemove Emulated mousedown Mouse Events mouseup click
  89. 89. Single Touch ≒ Click touchstart Touch Events touchend 300ms delay mouseover mousemove Emulated mousedown Mouse Events mouseup click
  90. 90. Touch 이벤트로 바꾸면 되겠네?
  91. 91. Ghost Click• Touch 이후에 click 발생• Clickable Element - Link - Form Button - onclick 이벤트 등록 element
  92. 92. Ghost Click• Touch 이후에 click 발생• Clickable Element - Link - Form Button - onclick 이벤트 등록 element
  93. 93. Ghost Click• event.preventDefault()로 마우스 이벤트 제거 - iOS 만 가능 - Android 는 불가• 해결책 - Busting Ghost Clicks - touch와 click 동시 발생 제거 - 모든 Clickable element의 click 이벤트 제거하고 touch 사용 - jQuery Mobile 등 라이브러리 사용
  94. 94. touch click
  95. 95. 기타• Galaxy S3 Browser, Android Chrome 에서는 - viewport 값을 user-scalable=no 로 설정하면 300ms 지연 없음• iOS 5 이하에서는 - DOM 위치를 변경하면 touch event handler가 사라짐 - 자식 element의 event handler는 유지
  96. 96. 마무리하며
  97. 97. 지금까지 언급한 내용• 화면 영역별 구현 방법• WebView & Web-App Bridge• Application Cache• 버전 관리• WebView Request Intercept• JS 크기 줄이기• Touch 이벤트
  98. 98. 캠프 하이브리드 앱• 개발 속도 - 상대적으로 빠르다 - Mobile Web Application의 Front-End 개발 역량에 따름• 실행 성능 - WebView 성능 한계 존재 - Native 성능 최적화 간과하면 안됨• 운영 - One Source Multi Use로 인해 테스트 부담 증가 - 앱/웹 버전 관리의 Best Practice 찾기 쉽지 않다
  99. 99. 마지막으로• 하이브리드 앱의 성능은 Web Application의 성능에 좌우• 하지만, 아무리 튜닝해도 2% 부족한 느낌• Facebook이 Native로 바꾸는 이 시점에...
  100. 100. WebView 이해를 위한 추천 강의• Android - Google I/O 2012 : Android WebView• iOS - WWDC 2012 : Optimizing Web Content in UIWebViews and Websites on iOS - WWDC 2012 : Debugging UIWebViews and Websites on iOS
  101. 101. 참고• Cross-Platform Tools: Build Once and Run Everywhere http://www.infoq.com/presentations/Cross-Platform-Mobile-Tools• Sunspider JavaScript Benchmark http://www.webkit.org/perf/sunspider/sunspider.html• Creating Fast Buttons for Mobile Web Applications https://developers.google.com/mobile/articles/fast_buttons• Substituting local data for remote UIWebView requests http://www.cocoawithlove.com/2010/09/substituting-local-data-for-remote.html• Esprima & Escodegen
  102. 102. 감사합니다.
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×