SlideShare ist ein Scribd-Unternehmen logo
1 von 64
Downloaden Sie, um offline zu lesen
(푸딩얼굴인식앱을통해서본)
하이브리드앱
아키텍쳐및개발사례
앱스프레소팀|장동수




                                         1
index

1. 하이브리드앱아키텍쳐개요

2. 하이브드리앱유형및특징

3. 푸딩얼굴인식앱개발사례공유

4. 앱스프레소플러그인활용

5. LessonsLearned

6. References


                                                           2
이런거...
아님-_-;




        하이브리드앱아키텍쳐개요
                                       3
하이브리드앱아키텍쳐구성요소

    네이티브                            하이브리드                                                  웹


     UI툴킷     웹UI툴킷          자바스크립트프레임웍/라이브러리

                                                       웹표준기술

    프레임웍
                              HTML5                          CSS                        자바스크립트



네이티브라이브러리          비표준DeviceAPIs                       표준DeviceAPIs


    개발도구             웹브라우져“엔진”                                  웹브라우져“앱”

                                    플랫폼SDK

    안드로이드
                    iOSSDK                 윈폰7SDK                          …⋯
     SDK


                                                                                                              4
하이브리드앱의꿈


ApplicationQuality

                             BEST           네이티브




                                    하이브리드




                             웹              WORST



                                               DevelopmentCost

                                                                         5
하이브리드라는이름의“짬뽕”...



                         난,
                    물~H2O~


    O


H             H




                                        6
앱개발자들을유혹하는“파란”짬뽕~



                                내가,
                               하이브리드~


    네이티브


웹                      웹




                                                        7
웹개발자들을유혹하는“빨간”짬뽕~



                                    나도,
                                  하이브리드~


           웹


네이티브                 네이티브




                                                           8
네이티브와웹의결합



                Flash/Flex?
                              Active-X?
JavaApplet?                    문제는....
                                        다리!!




                      Native-Web
네이티브                    Bridge             웹




                                                                 9
네이티브와웹의결합



                                WebView
WebViewClientWebChromeClient
                                    loadUrl
                 addJavascriptInterface



              UIWebView
              UIWebViewDelegate
              loadRequest
              stringByEvaluatingJavascriptFromString



                                                                      10
네이티브와웹의결합



                      자바스크립트
         캐시
              그래봤자,
              문자열~
       URL                      쿠키
어차피,
꼼수
                                          그리고...
                                         HTTP!




                        그림 출처: http://petticoatsandpistols.com/2010/05/12/

                                                                        11
하이브리드앱유형및특징
                                      12
네이티브지향하이브리드앱




             사실상네이티브,
                웹은거들뿐...
· 제한적이고직관적인네이티브와웹의결합
· 웹브라우져as-aUI컴포넌트
· 도움말,앱/개발사소개,공지사항/새소식...
· 웹기반사용자인증(OAuth)...
                                                                13
웹브라우져as-aUI컴포넌트




                                          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 : 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
예제코드(안드로이드)
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
예제코드(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
웹지향하이브리드앱




                        사실상웹,
           네이티브는거들뿐...
· 광범위하지만일관성있는네이티브와웹의결합
· 클라이언트사이드“웹앱”
· 기존웹서버+RESTfulAPI서버
· 기본적인웹컨텐츠는앱에포함
                                                                            25
하이브리드모바일앱프레임웍




                                        26
이것도하이브리드!




                    27
푸딩얼굴인식앱개발사례공유
                                        28
“푸딩얼굴인식앱”소개




         푸딩얼굴인식앱은...
· 600만+다운로드!
· @iolothebard와@seti222
· 5500+줄의자바스립트
· 2700+줄의CSS
· 200+줄의네이티브코드
· 앱스프레소0.9+내부개발버전
                                                                                                           29
단일페이지인터페이스



            index.html                 #pageId
                                      pageId.css
                                       pageId.js
             Active
            Page


show/hide



                                                      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
        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.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”HTMLMarkup




  자바스크립트                                    API서버                 웹서버

          HTML
                                                        로컬
                                     캐시                스토리지           캐시             앱
StaticHTMLFragment



   Placeholder                                Data                      Template


                                                                  +
  이름 클래스 레벨                           ★          ♥                    이름 클래스 레벨
   iolo      bard       만렙           iolo       bard    만렙                foreach
 장동수 개발자 쪼렙                          장동수 개발자 쪼렙                       ★       ♥
    ...        ...        ...         ...        ...        ...            end




                                                                                             33
“Placeholder”HTMLMarkup



          웹앱도MVC가필요해!
                    뷰와모델의분리
· HTML마크업을위한“변수”
· 웹서버개발에서클라이언트에적용
  smarty,...)을웹
                                         널리쓰이는템플릿(velocity,


· 서버부하는DOWN!
· 데이터전송량도DOWN!
· 캐시히트율은UP!
                                                                                      34
“Placeholder”HTMLMarkup

div 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
/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”CSSClass


        웹앱도MVC가필요해!
          뷰와컨트롤러의분리
· CSS스타일시트를위한“조건문”
· 프론트엔드UI개발자와자바스크립트개발자의약속
· 화면의동적인변화를자바스크립트없이확인제어
· HTML/CSS는“쬐끔”복잡해지고...-_-;
· 자바스크립트는“쬐끔”단순해지고...-_-;
· 함께일하기는더좋아지고~^O^
                                                                                   36
“Marker”CSSClass
스타일시트
/* 일치하는 연예인이 하나라도 있으면 안내문을 표시하지 않는다 */
#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
단말해상도별최적화



     ResponsiveWebDesign?
지금은곤란하니,기다려달라~
· HTML태그에마커CSS클래스추가/활용
· 기본적으로해상도에자유로운프론트엔드UI개발
· 널리쓰이는해상도는꼼꼼하게“미세”조정
· 특이한해상도는최소한의“미세”조정
· 이한몸희생해서...모두가행복할수있다면...ㅠㅠ
                                                                                              38
“Marker”CSSClass

단말 플랫폼/해상도 마커 클래스 초기화 스크립트
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
다국어처리



            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”,
                                                  ...
다국어 지원 자리 잡기 태그
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
Ant를이용한빌드자동화

1.verify:jslint

2.merge:antconcat

3.compress:YUICompressor

4.preprocess:antfilter

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=”#xFEFF;”/
        /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
앱스프레소플러그인구조


                                            AppspressoPlugin               Appspresso
Appspresso                              Project                                      Plugin
Application
                                                                                            Archive
  Project                               axplugin.xml           axplugin.js
                                                                                            (*.axp)
                                                 res              overlay

    link                                                                                     export
   run                                lib*.a              *.jar
                                                                                             share


    iOSNativeModule                              AndroidNativeModule
   (XcodeStaticLibraryProject)                 (AndroidLibraryProject)




                                                                                                                 48
앱스프레소플러그인API

             AxPlugin                                   AxPluginContext
                                                                intgetId()
/*YOURCODEHERE*/   2
                                                           StringgetMethod()
 activate(AxRuntimeContext)                               Object[]getParams()
deactivate(AxRuntimeContext)                                                ...
  execute(AxPluginContext)                                sendResult([result])
              ...                                      sendError(code[,message])


                             2                                             4

 AxRuntimeContext                                  1
                                                       자바스크립트API
         getWebView()
          getWidget()
               ...
     requirePlugin(pluginId)                            플랫폼확장API
    executeJavaScript(script)
               ...                                     Android                  iOS



                                                                                           49
앱스프레소플러그인플랫폼확장API


·안드로이드전용
 · Activity
 · ActivityListener
 · WebViewListener
 · WebViewClientListener/WebChromeClientListener
· iOS전용
 · UIViewController
 · UIApplicationDelegate
 · UIWebViewDelegate
 · AxViewControllerDelegate
                                                                      50
앱스프레소플러그인자바스크립트API



·AxPlugin의자바스크립트“stub”
 · functionexecSync(method,params)
 · functionexecAsync(method,successCallback,
   errorCallback,params)
    ·   params:arrayofarguments

    ·   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”
!    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
axplugin.js

/*jslint browser:true, confusion:true,     /**
debug:true, devel:true, nomen:true,         * pick a contact
plusplus: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
com...contact.ContactPlugin.java

package 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 extends
DefaultAxPlugin {                                     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
com...contact.ContactUtils.java

package com.appspresso.screw.contact;                     String contactId =
                                                                 data.getData().getLastPathSegment();
import java.util.ArrayList;
import java.util.List;                                JSONObject contact =
                                                  ContactUtils.getContactWithContactId(
import org.apache.commons.logging.Log;                                      activity, contactId);
import org.json.JSONArray;
import org.json.JSONObject;                               if (contact != null) {
                                                            runtimeContex.invokeJavaScriptFunction(
import android.app.Activity;                                     JS_CALLBACK_ONPICKCONTACT, contact);
import android.content.Intent;                            }
import android.database.Cursor;                           return true;
import android.provider.ContactsContract;             }
...
중략                                                static JSONObject getContactWithContactId(
...                                                           Activity activity, String contactId) {
import android.webkit.WebView;                          JSONObject contact = new JSONObject();
                                                        ...
import com.appspresso.api.AxLog;                        Cursor cursor = null;
                                                        Cursor rawContactIdsCursor = null;
class ContactUtils {                                    try {
  private static Log L =                                  String[] rawContactIds = null;
                 AxLog.getLog(“ContactPlugin”);           rawContactIdsCursor = activity
                                                            .getContentResolver().query(
 private static final String                                    RawContacts.CONTENT_URI,
                   JS_CALLBACK_ONPICKCONTACT =                 new String[] { RawContacts._ID },
               “ax.ext.contact.onPickContact”;                 RawContacts.CONTACT_ID + “ = ?”,
                                                               new String[] { contactId },
 public static boolean onPickContact(                          null);
                RuntimeContext runtimeContext,          ...
                                Intent data) {          중략
   if (data == null) {                                  ...
     return false;                                    }
   }                                              }




                                                                                                        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 : NSObjectAxPlugin, ABPeoplePickerNavigationControllerDelegate {
@private
    NSObjectAxRuntimeContext *_runtimeContext;
}

@property (nonatomic,readonly,retain) NSObjectAxRuntimeContext* runtimeContext;

- (void)activate:(NSObjectAxRuntimeContext*)runtimeContext;
- (void)deactivate:(NSObjectAxRuntimeContext*)runtimeContext;
- (void)execute:(NSObjectAxPluginContext*)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

//                                                    [self
//   ax_ext_contact_MyPlugin.m                    presentABPeoplePickerNavigationController];
//                                                    [context sendResult];
//   Copyright 2011 none. All rights reserved.      }
//                                                  else {
                                                      [context sendError:AX_NOT_AVAILABLE_ERR];
#import   “AxRuntimeContext.h”                      }
#import   “AxPluginContext.h”                     }
#import   “AxError.h”
#import   “AxLog.h”                               -
#import   “ax_ext_contact_MyPlugin.h”             (IBAction)presentABPeoplePickerNavigationContr
                                                  oller {
#define JS_CALLBACK_ONPICKCONTACT                    dispatch_async(dispatch_get_main_queue(), ^{
@”ax.ext.contact.onPickContact”                       ABPeoplePickerNavigationController *picker
                                                  = [[[ABPeoplePickerNavigationController alloc]
@implementation ax_ext_contact_MyPlugin           init] autorelease];
                                                      picker.peoplePickerDelegate = self;
@synthesize runtimeContext = _runtimeContext;         [[self.runtimeContext getViewController]
                                                  presentModalViewController:picker
- (void)activate:                                 animated:YES];
  (NSObjectAxRuntimeContext*)runtimeContext {       // [picker release];
   _runtimeContext = [runtimeContext retain];       });
}                                                 }
                                                  ...
- (void)deactivate:                               중략
  (NSObjectAxRuntimeContext*)runtimeContext {   ...
   [_runtimeContext release];                     -
   _runtimeContext = nil;                         (void)peoplePickerNavigationControllerDidCance
}                                                 l:(ABPeoplePickerNavigationController
                                                  *)peoplePicker {
- (void)execute:(idAxPluginContext)context {      [[self.runtimeContext getViewController]
  NSString* method = [context getMethod];         dismissModalViewControllerAnimated:YES];
                                                  }
  AX_LOG_TRACE(@”ContactView_ios_method : %s”,
method);                                          @end
  if([method isEqualToString:@”pickContact”]){




                                                                                                   58
LessonsLearned
                         59
to.개발자


[경고]이웹은당신이알았던그웹이아닙니다.

                        TheWebisDead.

             크로스플랫폼?새로운플랫폼!!

            이자바스크립트는당신이알았던
                그자바스크립트가아닙니다.

  웹요소기술+(GUI)애플리케이션아키텍쳐

                                        ...
                                                                                             60
cc.기획자,디자이너,...


[경고]이웹은당신이알았던그웹이아닙니다.

                       TheWebisDead.

                         저비용고품질?!

웹의한계,장점,단점을고려한기획디자인과
                    적절한품질목표설정

  무작정네이티브앱의UI/UX따라하기금지!

                                       ...
                                                                                             61
References



·   하이브리드모바일앱프레임웍http://slideshare.net/iolo/hybrid-mobile-application-framework

·   단일페이지인터페이스웹/앱개발http://slideshare.net/iolo/ss-7719322

·   AndroidSDKWebView레퍼런스http://goo.gl/iqr9H

·   iOSSDKUIWebView레퍼런스http://goo.gl/U8XGy

·   Android용하이브리드앱템플릿https://github.com/iolo/hellowebapp-android

·   HowtobuildAndroidAppwithHTML/CSS/JavaScripthttp://youtube.com/watch?v=uVqp1zcMfbE

·   iOS용하이브리드앱템플릿https://github.com/iolo/hellowebapp-ios

·   HowtobuildiOSAppwithHTML/CSS/JavaScripthttp://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’sallfolks...
                                    63
감사합니다
모바일개발실 / 앱스프레소팀 / 장동수
  iolothebard at kthcorp dot com
           @iolothebard




                                   64

Weitere ähnliche Inhalte

Was ist angesagt? (20)

淺談C#物件導向與DesignPattern.pdf
淺談C#物件導向與DesignPattern.pdf淺談C#物件導向與DesignPattern.pdf
淺談C#物件導向與DesignPattern.pdf
 
Project Reactor Now and Tomorrow
Project Reactor Now and TomorrowProject Reactor Now and Tomorrow
Project Reactor Now and Tomorrow
 
Earnest Money
Earnest MoneyEarnest Money
Earnest Money
 
Pile foundation
Pile foundationPile foundation
Pile foundation
 
Chapter 3 caisson
Chapter 3 caissonChapter 3 caisson
Chapter 3 caisson
 
RETAINING WALL
RETAINING WALLRETAINING WALL
RETAINING WALL
 
The Home Construction Process
The Home Construction ProcessThe Home Construction Process
The Home Construction Process
 
React Native
React NativeReact Native
React Native
 
24 Sitecore Tips that Every Sitecore Architect Needs to Know
24 Sitecore Tips that Every Sitecore Architect Needs to Know24 Sitecore Tips that Every Sitecore Architect Needs to Know
24 Sitecore Tips that Every Sitecore Architect Needs to Know
 
Combined Footing
Combined FootingCombined Footing
Combined Footing
 
5 Tips for Better JavaScript
5 Tips for Better JavaScript5 Tips for Better JavaScript
5 Tips for Better JavaScript
 
BCT MODULE 2 - Caisson
BCT MODULE 2 - CaissonBCT MODULE 2 - Caisson
BCT MODULE 2 - Caisson
 
retaining walls (ppt)
retaining walls (ppt)retaining walls (ppt)
retaining walls (ppt)
 
Presentation1.pptx
Presentation1.pptxPresentation1.pptx
Presentation1.pptx
 
rate analysis
rate analysisrate analysis
rate analysis
 
Compose Camp - Jetpack Compose for Android Developers Introduction Session De...
Compose Camp - Jetpack Compose for Android Developers Introduction Session De...Compose Camp - Jetpack Compose for Android Developers Introduction Session De...
Compose Camp - Jetpack Compose for Android Developers Introduction Session De...
 
pile wall
pile wallpile wall
pile wall
 
Flutter
FlutterFlutter
Flutter
 
Pile foundation
Pile foundationPile foundation
Pile foundation
 
ALTA Land Title Survey
ALTA Land Title Survey ALTA Land Title Survey
ALTA Land Title Survey
 

Andere mochten auch

C1 하이브리드 앱 어떻게 개발해야 하나
C1 하이브리드 앱 어떻게 개발해야 하나C1 하이브리드 앱 어떻게 개발해야 하나
C1 하이브리드 앱 어떻게 개발해야 하나NAVER D2
 
하이브리드앱 아키텍쳐 및 개발 사례
하이브리드앱 아키텍쳐 및 개발 사례하이브리드앱 아키텍쳐 및 개발 사례
하이브리드앱 아키텍쳐 및 개발 사례동수 장
 
하이브리드 앱_개발_개요
하이브리드 앱_개발_개요하이브리드 앱_개발_개요
하이브리드 앱_개발_개요BongSoo Jang
 
[124] 하이브리드 앱 개발기 김한솔
[124] 하이브리드 앱 개발기 김한솔[124] 하이브리드 앱 개발기 김한솔
[124] 하이브리드 앱 개발기 김한솔NAVER D2
 
107 3조 전기 안전 발표
107 3조 전기 안전 발표107 3조 전기 안전 발표
107 3조 전기 안전 발표980413
 
하이브리드앱 개발 전략과 이슈
하이브리드앱 개발 전략과 이슈하이브리드앱 개발 전략과 이슈
하이브리드앱 개발 전략과 이슈동수 장
 
모바일 앱 개발을 위한 Agile 적용
모바일 앱 개발을 위한 Agile 적용모바일 앱 개발을 위한 Agile 적용
모바일 앱 개발을 위한 Agile 적용Kevin Kim
 
하이브리드 앱 개발 개요
하이브리드 앱 개발 개요하이브리드 앱 개발 개요
하이브리드 앱 개발 개요Sohee Jeong
 
[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridappNAVER D2
 
[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신NAVER D2
 
Native vs. Web vs. Hybrid: Mobile Development Choices
Native vs. Web vs. Hybrid: Mobile Development ChoicesNative vs. Web vs. Hybrid: Mobile Development Choices
Native vs. Web vs. Hybrid: Mobile Development ChoicesJason Grigsby
 
모바일앱개발 교육자료
모바일앱개발 교육자료모바일앱개발 교육자료
모바일앱개발 교육자료JinHyuck Churn
 
메이커팀 워크샵1차 pdf용
메이커팀 워크샵1차 pdf용메이커팀 워크샵1차 pdf용
메이커팀 워크샵1차 pdf용KIM_MinHo
 
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...VMworld
 
HP 모바일 앱 테스트 자동화 솔루션 소개
HP 모바일 앱 테스트 자동화 솔루션 소개HP 모바일 앱 테스트 자동화 솔루션 소개
HP 모바일 앱 테스트 자동화 솔루션 소개Ki Bae Kim
 
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0KTH
 
서울열린데이터광장Db활용과 big data
서울열린데이터광장Db활용과 big data서울열린데이터광장Db활용과 big data
서울열린데이터광장Db활용과 big dataSung Woo Leem
 
웹캣소개서 (스마트교육연구소)
웹캣소개서 (스마트교육연구소)웹캣소개서 (스마트교육연구소)
웹캣소개서 (스마트교육연구소)tekville2
 

Andere mochten auch (20)

C1 하이브리드 앱 어떻게 개발해야 하나
C1 하이브리드 앱 어떻게 개발해야 하나C1 하이브리드 앱 어떻게 개발해야 하나
C1 하이브리드 앱 어떻게 개발해야 하나
 
하이브리드앱 아키텍쳐 및 개발 사례
하이브리드앱 아키텍쳐 및 개발 사례하이브리드앱 아키텍쳐 및 개발 사례
하이브리드앱 아키텍쳐 및 개발 사례
 
하이브리드 앱_개발_개요
하이브리드 앱_개발_개요하이브리드 앱_개발_개요
하이브리드 앱_개발_개요
 
[124] 하이브리드 앱 개발기 김한솔
[124] 하이브리드 앱 개발기 김한솔[124] 하이브리드 앱 개발기 김한솔
[124] 하이브리드 앱 개발기 김한솔
 
107 3조 전기 안전 발표
107 3조 전기 안전 발표107 3조 전기 안전 발표
107 3조 전기 안전 발표
 
하이브리드앱 개발 전략과 이슈
하이브리드앱 개발 전략과 이슈하이브리드앱 개발 전략과 이슈
하이브리드앱 개발 전략과 이슈
 
모바일 앱 개발을 위한 Agile 적용
모바일 앱 개발을 위한 Agile 적용모바일 앱 개발을 위한 Agile 적용
모바일 앱 개발을 위한 Agile 적용
 
하이브리드 앱 개발 개요
하이브리드 앱 개발 개요하이브리드 앱 개발 개요
하이브리드 앱 개발 개요
 
[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp[D2 오픈세미나]3.web view hybridapp
[D2 오픈세미나]3.web view hybridapp
 
[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신[D2 오픈세미나]4.네이티브앱저장통신
[D2 오픈세미나]4.네이티브앱저장통신
 
Native vs. Web vs. Hybrid: Mobile Development Choices
Native vs. Web vs. Hybrid: Mobile Development ChoicesNative vs. Web vs. Hybrid: Mobile Development Choices
Native vs. Web vs. Hybrid: Mobile Development Choices
 
모바일앱개발 교육자료
모바일앱개발 교육자료모바일앱개발 교육자료
모바일앱개발 교육자료
 
메이커팀 워크샵1차 pdf용
메이커팀 워크샵1차 pdf용메이커팀 워크샵1차 pdf용
메이커팀 워크샵1차 pdf용
 
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...
VMworld 2013: US Air National Guard - DoD Private Cloud Initiative –How Virtu...
 
Mobile Application Development Platform "Morpheus"
Mobile Application Development Platform "Morpheus"Mobile Application Development Platform "Morpheus"
Mobile Application Development Platform "Morpheus"
 
HP 모바일 앱 테스트 자동화 솔루션 소개
HP 모바일 앱 테스트 자동화 솔루션 소개HP 모바일 앱 테스트 자동화 솔루션 소개
HP 모바일 앱 테스트 자동화 솔루션 소개
 
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0
H3 2011 하이브리드 앱의 미래, 앱스프레소 1.0
 
서울열린데이터광장Db활용과 big data
서울열린데이터광장Db활용과 big data서울열린데이터광장Db활용과 big data
서울열린데이터광장Db활용과 big data
 
웹캣소개서 (스마트교육연구소)
웹캣소개서 (스마트교육연구소)웹캣소개서 (스마트교육연구소)
웹캣소개서 (스마트교육연구소)
 
Hybrid App
Hybrid AppHybrid App
Hybrid App
 

Ähnlich wie H3 2011 하이브리드 앱 아키텍쳐 및 개발방법

H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수
H3  2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수H3  2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수
H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수KTH, 케이티하이텔
 
T12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React Native
T12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React NativeT12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React Native
T12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React Native양재동 코드랩
 
하이브리드 앱(Hybrid App)
하이브리드 앱(Hybrid App)하이브리드 앱(Hybrid App)
하이브리드 앱(Hybrid App)Changhwan Yi
 
차세대 웹비즈니스를 위한 "HTML5"
차세대 웹비즈니스를 위한 "HTML5"차세대 웹비즈니스를 위한 "HTML5"
차세대 웹비즈니스를 위한 "HTML5"Changhwan Yi
 
Appview 소개
Appview 소개Appview 소개
Appview 소개logeo
 
Introduction to WApplE.js - 트루모바일
Introduction to WApplE.js - 트루모바일Introduction to WApplE.js - 트루모바일
Introduction to WApplE.js - 트루모바일TRUEMobile
 
Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유
Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유
Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유Sang Seok Lim
 
01.모바일 프레임워크 이론
01.모바일 프레임워크 이론01.모바일 프레임워크 이론
01.모바일 프레임워크 이론Hankyo
 
하이브리드앱 성능 극복
하이브리드앱 성능 극복하이브리드앱 성능 극복
하이브리드앱 성능 극복sung hwan Park
 
하이브리드앱 성능 극복
하이브리드앱 성능 극복하이브리드앱 성능 극복
하이브리드앱 성능 극복Mu-ik Jeon
 
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략Kris Jeong
 
Data-binding AngularJS
Data-binding AngularJSData-binding AngularJS
Data-binding AngularJSEunYoung Kim
 
04.모바일 device api_실습교재
04.모바일 device api_실습교재04.모바일 device api_실습교재
04.모바일 device api_실습교재Hankyo
 
Hybrid App Platform - HyWAI 3.5
Hybrid App Platform - HyWAI 3.5Hybrid App Platform - HyWAI 3.5
Hybrid App Platform - HyWAI 3.5Jonathan Jeon
 
F3 네이버오픈api만드는매쉬업
F3 네이버오픈api만드는매쉬업F3 네이버오픈api만드는매쉬업
F3 네이버오픈api만드는매쉬업NAVER D2
 
Progressive Web Apps
Progressive Web AppsProgressive Web Apps
Progressive Web Appsjungkees
 
Web app 개발 방법론
Web app 개발 방법론Web app 개발 방법론
Web app 개발 방법론Sang Seok Lim
 
React native development
React native developmentReact native development
React native developmentSangSun Park
 
Mozilla 오픈 웹 모바일 플랫폼 (2012)
Mozilla 오픈 웹 모바일 플랫폼 (2012)Mozilla 오픈 웹 모바일 플랫폼 (2012)
Mozilla 오픈 웹 모바일 플랫폼 (2012)Channy Yun
 

Ähnlich wie H3 2011 하이브리드 앱 아키텍쳐 및 개발방법 (20)

H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수
H3  2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수H3  2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수
H3 2011 하이브리드 앱 아키텍쳐 및 개발방법_아임IN Lab팀_장동수
 
T12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React Native
T12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React NativeT12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React Native
T12_1_김나람_웹 기술로 구축하는 모바일 애플리케이션 - React Native
 
하이브리드 앱(Hybrid App)
하이브리드 앱(Hybrid App)하이브리드 앱(Hybrid App)
하이브리드 앱(Hybrid App)
 
차세대 웹비즈니스를 위한 "HTML5"
차세대 웹비즈니스를 위한 "HTML5"차세대 웹비즈니스를 위한 "HTML5"
차세대 웹비즈니스를 위한 "HTML5"
 
Appview 소개
Appview 소개Appview 소개
Appview 소개
 
Introduction to WApplE.js - 트루모바일
Introduction to WApplE.js - 트루모바일Introduction to WApplE.js - 트루모바일
Introduction to WApplE.js - 트루모바일
 
Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유
Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유
Angularjs, ionic, cordova 기반 syrup store app 개발 사례 공유
 
01.모바일 프레임워크 이론
01.모바일 프레임워크 이론01.모바일 프레임워크 이론
01.모바일 프레임워크 이론
 
하이브리드앱 성능 극복
하이브리드앱 성능 극복하이브리드앱 성능 극복
하이브리드앱 성능 극복
 
하이브리드앱 성능 극복
하이브리드앱 성능 극복하이브리드앱 성능 극복
하이브리드앱 성능 극복
 
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
Soscon2017 오픈소스를 활용한 마이크로 서비스의 캐시 전략
 
Data-binding AngularJS
Data-binding AngularJSData-binding AngularJS
Data-binding AngularJS
 
04.모바일 device api_실습교재
04.모바일 device api_실습교재04.모바일 device api_실습교재
04.모바일 device api_실습교재
 
요즘웹개발
요즘웹개발요즘웹개발
요즘웹개발
 
Hybrid App Platform - HyWAI 3.5
Hybrid App Platform - HyWAI 3.5Hybrid App Platform - HyWAI 3.5
Hybrid App Platform - HyWAI 3.5
 
F3 네이버오픈api만드는매쉬업
F3 네이버오픈api만드는매쉬업F3 네이버오픈api만드는매쉬업
F3 네이버오픈api만드는매쉬업
 
Progressive Web Apps
Progressive Web AppsProgressive Web Apps
Progressive Web Apps
 
Web app 개발 방법론
Web app 개발 방법론Web app 개발 방법론
Web app 개발 방법론
 
React native development
React native developmentReact native development
React native development
 
Mozilla 오픈 웹 모바일 플랫폼 (2012)
Mozilla 오픈 웹 모바일 플랫폼 (2012)Mozilla 오픈 웹 모바일 플랫폼 (2012)
Mozilla 오픈 웹 모바일 플랫폼 (2012)
 

Mehr von KTH

H3 2011 모바일에서의 Location API 완전정복
H3 2011 모바일에서의 Location API 완전정복H3 2011 모바일에서의 Location API 완전정복
H3 2011 모바일에서의 Location API 완전정복KTH
 
H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요KTH
 
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례KTH
 
H3 2011 하이브리드 클라우드 활용방안 및 도입전략
H3 2011 하이브리드 클라우드 활용방안 및 도입전략H3 2011 하이브리드 클라우드 활용방안 및 도입전략
H3 2011 하이브리드 클라우드 활용방안 및 도입전략KTH
 
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략KTH
 
H3 2011 흰머리 성성하게 개발하기 위해
H3 2011 흰머리 성성하게 개발하기 위해H3 2011 흰머리 성성하게 개발하기 위해
H3 2011 흰머리 성성하게 개발하기 위해KTH
 
H3 2011 UX에 대한 7가지 오해와 진실
H3 2011 UX에 대한 7가지 오해와 진실H3 2011 UX에 대한 7가지 오해와 진실
H3 2011 UX에 대한 7가지 오해와 진실KTH
 
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택KTH
 
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략KTH
 
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래KTH
 
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!KTH
 
H3 2011 모바일 시대의 Search Engine Optimization 전략
H3 2011 모바일 시대의 Search Engine Optimization 전략H3 2011 모바일 시대의 Search Engine Optimization 전략
H3 2011 모바일 시대의 Search Engine Optimization 전략KTH
 
H3 2011 반응형 웹디자인, 진짜 할 만 한가?
H3 2011 반응형 웹디자인, 진짜 할 만 한가?H3 2011 반응형 웹디자인, 진짜 할 만 한가?
H3 2011 반응형 웹디자인, 진짜 할 만 한가?KTH
 
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래KTH
 

Mehr von KTH (14)

H3 2011 모바일에서의 Location API 완전정복
H3 2011 모바일에서의 Location API 완전정복H3 2011 모바일에서의 Location API 완전정복
H3 2011 모바일에서의 Location API 완전정복
 
H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요H3 2011 파이썬으로 클라우드 하고 싶어요
H3 2011 파이썬으로 클라우드 하고 싶어요
 
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례
H3 2011 iOS5 새로운 기능들의 프로젝트 적용 사례
 
H3 2011 하이브리드 클라우드 활용방안 및 도입전략
H3 2011 하이브리드 클라우드 활용방안 및 도입전략H3 2011 하이브리드 클라우드 활용방안 및 도입전략
H3 2011 하이브리드 클라우드 활용방안 및 도입전략
 
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략
H3 2011 안드로이드의 Seamless UX를 위한 Activity 활용전략
 
H3 2011 흰머리 성성하게 개발하기 위해
H3 2011 흰머리 성성하게 개발하기 위해H3 2011 흰머리 성성하게 개발하기 위해
H3 2011 흰머리 성성하게 개발하기 위해
 
H3 2011 UX에 대한 7가지 오해와 진실
H3 2011 UX에 대한 7가지 오해와 진실H3 2011 UX에 대한 7가지 오해와 진실
H3 2011 UX에 대한 7가지 오해와 진실
 
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택
H3 2011 클라우드 컴퓨팅 AWS 글로벌 서비스 구축을 위한 선택
 
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략
H3 2011 대형사이트 구축을 위한 MySQL 튜닝전략
 
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래
H3 2011 Google을 통해 살펴보는 분산 파일 시스템의 현재와 미래
 
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!
H3 2011 HTML/CSS로 만들어진 디테일이 살아있는 앱 제작의 노하우를 모두 공개합니다!
 
H3 2011 모바일 시대의 Search Engine Optimization 전략
H3 2011 모바일 시대의 Search Engine Optimization 전략H3 2011 모바일 시대의 Search Engine Optimization 전략
H3 2011 모바일 시대의 Search Engine Optimization 전략
 
H3 2011 반응형 웹디자인, 진짜 할 만 한가?
H3 2011 반응형 웹디자인, 진짜 할 만 한가?H3 2011 반응형 웹디자인, 진짜 할 만 한가?
H3 2011 반응형 웹디자인, 진짜 할 만 한가?
 
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래
H3 2011 앱 개발에 날개를 달자, 모바일 클라우드가 꿈꾸는 미래
 

H3 2011 하이브리드 앱 아키텍쳐 및 개발방법

  • 2. index 1. 하이브리드앱아키텍쳐개요 2. 하이브드리앱유형및특징 3. 푸딩얼굴인식앱개발사례공유 4. 앱스프레소플러그인활용 5. LessonsLearned 6. References 2
  • 3. 이런거... 아님-_-; 하이브리드앱아키텍쳐개요 3
  • 4. 하이브리드앱아키텍쳐구성요소 네이티브 하이브리드 웹 UI툴킷 웹UI툴킷 자바스크립트프레임웍/라이브러리 웹표준기술 프레임웍 HTML5 CSS 자바스크립트 네이티브라이브러리 비표준DeviceAPIs 표준DeviceAPIs 개발도구 웹브라우져“엔진” 웹브라우져“앱” 플랫폼SDK 안드로이드 iOSSDK 윈폰7SDK …⋯ SDK 4
  • 5. 하이브리드앱의꿈 ApplicationQuality BEST 네이티브 하이브리드 웹 WORST DevelopmentCost 5
  • 7. 앱개발자들을유혹하는“파란”짬뽕~ 내가, 하이브리드~ 네이티브 웹 웹 7
  • 8. 웹개발자들을유혹하는“빨간”짬뽕~ 나도, 하이브리드~ 웹 네이티브 네이티브 8
  • 9. 네이티브와웹의결합 Flash/Flex? Active-X? JavaApplet? 문제는.... 다리!! Native-Web 네이티브 Bridge 웹 9
  • 10. 네이티브와웹의결합 WebView WebViewClientWebChromeClient loadUrl addJavascriptInterface UIWebView UIWebViewDelegate loadRequest stringByEvaluatingJavascriptFromString 10
  • 11. 네이티브와웹의결합 자바스크립트 캐시 그래봤자, 문자열~ URL 쿠키 어차피, 꼼수 그리고... HTTP! 그림 출처: http://petticoatsandpistols.com/2010/05/12/ 11
  • 13. 네이티브지향하이브리드앱 사실상네이티브, 웹은거들뿐... · 제한적이고직관적인네이티브와웹의결합 · 웹브라우져as-aUI컴포넌트 · 도움말,앱/개발사소개,공지사항/새소식... · 웹기반사용자인증(OAuth)... 13
  • 16. 예제코드(안드로이드) 웹서버 컨텐츠 불러오기 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
  • 17. 예제코드(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. “한지붕두가족”하이브리드앱 웹은아니지만네이티브도아닌, 그러나웹스러운... · 광범위하고일관성없는네이티브와웹의결합 · 웹브라우져를내장한네이티브클라이언트 · 기존웹서버“조금손봐서...”재활용 · 기존웹컨텐츠“조금손봐서...”재활용 18
  • 21. 예제코드(안드로이드) 링크 클릭 가로채기 ... 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
  • 22. 예제코드(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
  • 23. 예제코드(안드로이드) 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
  • 24. 예제코드(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
  • 25. 웹지향하이브리드앱 사실상웹, 네이티브는거들뿐... · 광범위하지만일관성있는네이티브와웹의결합 · 클라이언트사이드“웹앱” · 기존웹서버+RESTfulAPI서버 · 기본적인웹컨텐츠는앱에포함 25
  • 29. “푸딩얼굴인식앱”소개 푸딩얼굴인식앱은... · 600만+다운로드! · @iolothebard와@seti222 · 5500+줄의자바스립트 · 2700+줄의CSS · 200+줄의네이티브코드 · 앱스프레소0.9+내부개발버전 29
  • 30. 단일페이지인터페이스 index.html #pageId pageId.css pageId.js Active Page show/hide 30
  • 31. 단일페이지인터페이스 웹앱도MVC가필요해! 자바스크립트가컨트롤러! · 웹서버도없는데...페이지이동은왜?! · (GUI)애플리케이션스타일의“상태”관리 · 빠른화면전환화면전환효과 · 체감성능UP! · 메모리사용량UP! · 그런데...공동작업은어떻게?-_-; 31
  • 32. 단일페이지인터페이스 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 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.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
  • 33. “Placeholder”HTMLMarkup 자바스크립트 API서버 웹서버 HTML 로컬 캐시 스토리지 캐시 앱 StaticHTMLFragment Placeholder Data Template + 이름 클래스 레벨 ★ ♥ 이름 클래스 레벨 iolo bard 만렙 iolo bard 만렙 foreach 장동수 개발자 쪼렙 장동수 개발자 쪼렙 ★ ♥ ... ... ... ... ... ... end 33
  • 34. “Placeholder”HTMLMarkup 웹앱도MVC가필요해! 뷰와모델의분리 · HTML마크업을위한“변수” · 웹서버개발에서클라이언트에적용 smarty,...)을웹 널리쓰이는템플릿(velocity, · 서버부하는DOWN! · 데이터전송량도DOWN! · 캐시히트율은UP! 34
  • 35. “Placeholder”HTMLMarkup div 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 /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
  • 36. “Marker”CSSClass 웹앱도MVC가필요해! 뷰와컨트롤러의분리 · CSS스타일시트를위한“조건문” · 프론트엔드UI개발자와자바스크립트개발자의약속 · 화면의동적인변화를자바스크립트없이확인제어 · HTML/CSS는“쬐끔”복잡해지고...-_-; · 자바스크립트는“쬐끔”단순해지고...-_-; · 함께일하기는더좋아지고~^O^ 36
  • 37. “Marker”CSSClass 스타일시트 /* 일치하는 연예인이 하나라도 있으면 안내문을 표시하지 않는다 */ #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
  • 38. 단말해상도별최적화 ResponsiveWebDesign? 지금은곤란하니,기다려달라~ · HTML태그에마커CSS클래스추가/활용 · 기본적으로해상도에자유로운프론트엔드UI개발 · 널리쓰이는해상도는꼼꼼하게“미세”조정 · 특이한해상도는최소한의“미세”조정 · 이한몸희생해서...모두가행복할수있다면...ㅠㅠ 38
  • 39. “Marker”CSSClass 단말 플랫폼/해상도 마커 클래스 초기화 스크립트 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
  • 40. 다국어처리 I18N?L10N?A11Y? 이제는선택이아닌필수! · HTML태그에마커CSS클래스추가/활용 · “그림글자”사용최소화:국제화접근성 · 일관성있는번역어식별자 · 언어별어순/길이/너비차이고려 · 언어별UI“미세”조정 40
  • 42. 다국어처리 다국어 지원 초기화 스크립트 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
  • 44. 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=”#xFEFF;”/ /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
  • 45. 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
  • 46. 네이티브와의결합 사용한앱스프레소플러그인 · deviceapis.filesystem:파일입출력 · deviceapis.deviceinteraction:화면꺼짐방지/진동 · ax.ext.media:카메라/포토앨범/효과음 · ax.ext.net:업로드/다운로드 · ax.ext.ui:네이티브UI/차일드브라우져 · ax.ext.ga:구글통계 · ax.ext.admob:애드몹광고 · kth.puddingface:푸딩얼굴인식앱전용*^^* 46
  • 48. 앱스프레소플러그인구조 AppspressoPlugin Appspresso Appspresso Project Plugin Application Archive Project axplugin.xml axplugin.js (*.axp) res overlay link export run lib*.a *.jar share iOSNativeModule AndroidNativeModule (XcodeStaticLibraryProject) (AndroidLibraryProject) 48
  • 49. 앱스프레소플러그인API AxPlugin AxPluginContext intgetId() /*YOURCODEHERE*/ 2 StringgetMethod() activate(AxRuntimeContext) Object[]getParams() deactivate(AxRuntimeContext) ... execute(AxPluginContext) sendResult([result]) ... sendError(code[,message]) 2 4 AxRuntimeContext 1 자바스크립트API getWebView() getWidget() ... requirePlugin(pluginId) 플랫폼확장API executeJavaScript(script) ... Android iOS 49
  • 50. 앱스프레소플러그인플랫폼확장API ·안드로이드전용 · Activity · ActivityListener · WebViewListener · WebViewClientListener/WebChromeClientListener · iOS전용 · UIViewController · UIApplicationDelegate · UIWebViewDelegate · AxViewControllerDelegate 50
  • 51. 앱스프레소플러그인자바스크립트API ·AxPlugin의자바스크립트“stub” · functionexecSync(method,params) · functionexecAsync(method,successCallback, errorCallback,params) · params:arrayofarguments · successCallback:function(result){...} · errorCallback:function(error){...} · ax,ax.error,ax.util,ax.console, ax.request,ax.bridge,ax.plugin,... 51
  • 52. 앱스프레소플러그인개발실습 네이티브주소록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
  • 53. 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
  • 54. axplugin.js /*jslint browser:true, confusion:true, /** debug:true, devel:true, nomen:true, * pick a contact plusplus: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
  • 55. com...contact.ContactPlugin.java package 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 extends DefaultAxPlugin { 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
  • 56. com...contact.ContactUtils.java package com.appspresso.screw.contact; String contactId = data.getData().getLastPathSegment(); import java.util.ArrayList; import java.util.List; JSONObject contact = ContactUtils.getContactWithContactId( import org.apache.commons.logging.Log; activity, contactId); import org.json.JSONArray; import org.json.JSONObject; if (contact != null) { runtimeContex.invokeJavaScriptFunction( import android.app.Activity; JS_CALLBACK_ONPICKCONTACT, contact); import android.content.Intent; } import android.database.Cursor; return true; import android.provider.ContactsContract; } ... 중략 static JSONObject getContactWithContactId( ... Activity activity, String contactId) { import android.webkit.WebView; JSONObject contact = new JSONObject(); ... import com.appspresso.api.AxLog; Cursor cursor = null; Cursor rawContactIdsCursor = null; class ContactUtils { try { private static Log L = String[] rawContactIds = null; AxLog.getLog(“ContactPlugin”); rawContactIdsCursor = activity .getContentResolver().query( private static final String RawContacts.CONTENT_URI, JS_CALLBACK_ONPICKCONTACT = new String[] { RawContacts._ID }, “ax.ext.contact.onPickContact”; RawContacts.CONTACT_ID + “ = ?”, new String[] { contactId }, public static boolean onPickContact( null); RuntimeContext runtimeContext, ... Intent data) { 중략 if (data == null) { ... return false; } } } 56
  • 57. 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 : NSObjectAxPlugin, ABPeoplePickerNavigationControllerDelegate { @private NSObjectAxRuntimeContext *_runtimeContext; } @property (nonatomic,readonly,retain) NSObjectAxRuntimeContext* runtimeContext; - (void)activate:(NSObjectAxRuntimeContext*)runtimeContext; - (void)deactivate:(NSObjectAxRuntimeContext*)runtimeContext; - (void)execute:(NSObjectAxPluginContext*)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
  • 58. ax_ext_contact_MyPlugin.m // [self // ax_ext_contact_MyPlugin.m presentABPeoplePickerNavigationController]; // [context sendResult]; // Copyright 2011 none. All rights reserved. } // else { [context sendError:AX_NOT_AVAILABLE_ERR]; #import “AxRuntimeContext.h” } #import “AxPluginContext.h” } #import “AxError.h” #import “AxLog.h” - #import “ax_ext_contact_MyPlugin.h” (IBAction)presentABPeoplePickerNavigationContr oller { #define JS_CALLBACK_ONPICKCONTACT dispatch_async(dispatch_get_main_queue(), ^{ @”ax.ext.contact.onPickContact” ABPeoplePickerNavigationController *picker = [[[ABPeoplePickerNavigationController alloc] @implementation ax_ext_contact_MyPlugin init] autorelease]; picker.peoplePickerDelegate = self; @synthesize runtimeContext = _runtimeContext; [[self.runtimeContext getViewController] presentModalViewController:picker - (void)activate: animated:YES]; (NSObjectAxRuntimeContext*)runtimeContext { // [picker release]; _runtimeContext = [runtimeContext retain]; }); } } ... - (void)deactivate: 중략 (NSObjectAxRuntimeContext*)runtimeContext { ... [_runtimeContext release]; - _runtimeContext = nil; (void)peoplePickerNavigationControllerDidCance } l:(ABPeoplePickerNavigationController *)peoplePicker { - (void)execute:(idAxPluginContext)context { [[self.runtimeContext getViewController] NSString* method = [context getMethod]; dismissModalViewControllerAnimated:YES]; } AX_LOG_TRACE(@”ContactView_ios_method : %s”, method); @end if([method isEqualToString:@”pickContact”]){ 58
  • 60. to.개발자 [경고]이웹은당신이알았던그웹이아닙니다. TheWebisDead. 크로스플랫폼?새로운플랫폼!! 이자바스크립트는당신이알았던 그자바스크립트가아닙니다. 웹요소기술+(GUI)애플리케이션아키텍쳐 ... 60
  • 61. cc.기획자,디자이너,... [경고]이웹은당신이알았던그웹이아닙니다. TheWebisDead. 저비용고품질?! 웹의한계,장점,단점을고려한기획디자인과 적절한품질목표설정 무작정네이티브앱의UI/UX따라하기금지! ... 61
  • 62. References · 하이브리드모바일앱프레임웍http://slideshare.net/iolo/hybrid-mobile-application-framework · 단일페이지인터페이스웹/앱개발http://slideshare.net/iolo/ss-7719322 · AndroidSDKWebView레퍼런스http://goo.gl/iqr9H · iOSSDKUIWebView레퍼런스http://goo.gl/U8XGy · Android용하이브리드앱템플릿https://github.com/iolo/hellowebapp-android · HowtobuildAndroidAppwithHTML/CSS/JavaScripthttp://youtube.com/watch?v=uVqp1zcMfbE · iOS용하이브리드앱템플릿https://github.com/iolo/hellowebapp-ios · HowtobuildiOSAppwithHTML/CSS/JavaScripthttp://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
  • 64. 감사합니다 모바일개발실 / 앱스프레소팀 / 장동수 iolothebard at kthcorp dot com @iolothebard 64