[ionic] Android 호환성

모바일 2015. 12. 21. 17:59

이번에는 Android와 관련된 내용입니다.


1. cross-walk, User-Agent 관련

이건 이전 글로 대체합니다.

http://resisa.tistory.com/170


2. 푸시 관련

여러가지 푸시 플러그인이 있는데 저는 아래 플러그인으로 최종 선택하였습니다.

아래 플러그인이 사용도 가장 심플하고 다른 플러그인에서 안되는 기능도 됩니다.

https://github.com/Code1Tech/phonegap-plugin-push


푸시에서 GCM토큰을 받아오는 초기화 코드의 경우 앱 실행 타이밍과 연관이 있습니다. 입이 실행 되자마자 해당 초기화 코드가 실행되면 토큰을 정상적으로 가져오지 못합니다. 그래서 setTimeout()를 사용하여 2.5초 정도의 딜레이를 주고 실행시키고 있습니다. 2초정도로 했을 경우 특정 디바이스에서 토큰을 받아오지 못하는 현상이 있어 해당 시간을 늘렸습니다.


요구사항 중 앱이 실행 중인 경우에도 푸시가 왔으면 좋겠다는 의견이 있었습니다. iOS 푸시에서는 앱이 실행 중일 때는 푸시가 올 수 없습니다. 그래서 일반적으로 iOS에서는 앱이 실행 중일 경우 푸시가 오는 것처럼 앱내에서 구현을 해주고 있습니다.

여튼 안드로이드에서 앱이 실행 중일 경우 푸시가 오도록 하려면 위의 플러그인을 사용하여야 하며 아래 코드를 추가해주어야 합니다.


- GCMIntentService.java

@Override  

 public void onMessageReceived(String from, Bundle extras) {

            // if we are in the foreground and forceShow is `false` only send data

            if (!forceShow && PushPlugin.isInForeground()) {

                Log.d(LOG_TAG, "foreground");

                extras.putBoolean(FOREGROUND, true);

                PushPlugin.sendExtras(extras);

            }

            // if we are in the foreground and forceShow is `true`, force show the notification if the data has at least a message or title

            else if (forceShow && PushPlugin.isInForeground()) {

                Log.d(LOG_TAG, "foreground force");

                extras.putBoolean(FOREGROUND, true);

                PushPlugin.sendExtras(extras);

                showNotificationIfPossible(getApplicationContext(), extras);

            }

            // if we are not in the foreground always send notification if the data has at least a message or title

            else {

                Log.d(LOG_TAG, "background");

                extras.putBoolean(FOREGROUND, false);


                showNotificationIfPossible(getApplicationContext(), extras);

            }

    }


3. ThemeableBrowser Plugin

iframe내에 링크가 걸려 있을 경우 인앱브라우저 화면에서 아무런 버튼도 보이지 않는 현상이 있습니다. 직접 해당 플러그인을 사용하는 코드에서는 상관없지만 iframe내의 링크의 경우에 해당하는 내용입니다. 아래처럼 자바 코드에 close관련된 부분만 변경하여 넣어줬습니다.

 public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {

            final Options features = parseFeature("{ closeButton: { image: 'close', imagePressed: 'close_pressed', align: 'right', event: 'closePressed'}}");

}


4. MenuButton 관련

Android에서는 메뉴 버튼과 뒤로 가기 버튼이 있어 해당 부분에 대해서도 신경을 써줘야 합니다. 아래 링크로 대신합니다. 메뉴 버튼의 경우 기기에 따라서 다르게 동작합니다.


- CordovaWebViewImpl.java

https://forum.ionicframework.com/t/android-hardware-menu-button-not-working-with-cordova-5-1-1-and-ionic-1-1-0/33128/3



Posted by resisa
,

[ionic] iOS 호환성

모바일 2015. 12. 19. 12:18

몇년 만인지 밤을 새면서 개발을 할 만큼 바쁜 한달이 지나가고 있습니다. ㅜㅜ 덕분에?! 생활이 엉망진창이 되었지만;; 이제 다시 여유가 조금 생겨 정리 차원에서 이렇게 글을 쓰고 있습니다.

이전 글에서 적었듯이 하이브리드로 개발을 하면서 웹을 iframe 넣어 세상에는 없는(있으면 안되는) 앱이 만들어졌습니다 

iOS와 관련된 부분들을 정리하고 Android로 정리해 볼 생각입니다.

1. iframe 관련

<ion-content scroll="false"> 

         <ion-content overflow-scroll="true" style="position:fixed;" on-scroll="getScrollPosition()">

              <iframe data-tap-disabled="true" header-shrink id="mobile_web" src="{{webUrl}}" style="left:0;top:0;height:100%;width:100%;background-color:#fff;" onLoad="uploadDone()" ></iframe>

          </ion-content>

      </ion-scroll>

    

    <div class="bar bar-footer">

    </div>

  </ion-content>

  • footer에 네비게이션을 넣기 위해서 <ion-content> 태그를 중복으로 사용하였습니다.
  • iframe내의 컨텐츠에 스크롤을 위해서 overflow-scroll=true로 지정하였으며 본문만 스크롤이 되어야 하므로 부모 <ion-content>에 scroll=false로 지정을 하였습니다.
  • iOS에서 iframe내의 스크롤이 내려갈 때 헤더부분에 백그라운드 색을 변경하는 해당 이벤트는 on-scroll 지시어를 통해서 가능합니다. iOS에서 스크롤 관련된 부분(현재 높이라던가)은 $ionicScrollDelegate를 활용하시면 됩니다.
  • iframe의 backgournd-color가 ionic의 스타일 때문인지 회색 계열로 보여 흰색으로 지정하였습니다.
  • css와 관련된 설정은 아래와 같습니다.
  • .overflow-scroll {

        overflow:auto;

      -webkit-overflow-scrolling: touch;

      top: 0;

      right: 0;

      bottom: 0;

      left: 0;

      position: absolute; }

      .overflow-scroll .scroll {

        position: static;

        height: 100%;

        width: 100%;

        -webkit-transform: translate3d(0, 0, 0); }

    색으로 표시한 부분이 ionic css에서 변경한 부분입니다. iframe내의 컨텐츠에 대한 사이즈와 연관된 부분으로 가로 스크롤이 가능하게 하려면 overflow:auto로 변경해줘야 하며 다음, 네이버 모바일 화면을 넣어봤는데 일부 화면에 대한 가로 사이즈를 맞춰주려면 width: 100%줘야합니다. 다음의 경우 기사를 클릭하면 사이즈 문제가 발생하며 네이버의 경우에는 일부 화면에서 가로 사이즈가 조금 짧게 표시되는거를 제외하고는 대부분 브라우저와 동일하게 표시됩니다.

  • data-tap-disabled=true 속성은 디바이스에서 터치와 네이티브, 자바스크립트 스크롤과 관련된 부분입니다. 자세한 내용은 해당 속성으로 검색하시면 ionic 사이트에 설명되어 있습니다.

2. ThemeableBrowser(InAppBrowser) Plugin
InAppBrowser를 확장시킨 플러그인으로 inAppBrowser 화면이 너무 올드해서 해당 플러그인을 사용하였는데 iOS에서는 url을 클릭하면 별도 화면이 아닌 해당 화면으로 이동, 대체가 되어 해당 내부 코드에서 아래 부분을 수정하였습니다.

- (void)open:(CDVInvokedUrlCommand*)command

{

//        if ([target isEqualToString:kThemeableBrowserTargetSelf]) {

//            [self openInCordovaWebView:absoluteUrl withOptions:options];

//        } else if ([target isEqualToString:kThemeableBrowserTargetSystem]) {

//            [self openInSystem:absoluteUrl];

//        } else { // _blank or anything else

            [self openInThemeableBrowser:absoluteUrl withOptions:options];

//        }

 }


3. NetworkInterface Plugin

ip주소를 가져오기 위한 플러그인 입니다. 해당 플러그인은 wi-fi일 경우에는 문제가 없지만 데이터로 ip를 가져오려고 하면 문제가 발생합니다. 플러그인이 Android용과 iOS용으로 나뉘어서 네이티브 코드들을 생성합니다. iOS에서 발생한 문제입니다.

 if([[NSString stringWithUTF8String:temp_addr->ifa_nameisEqualToString:@"en0"

|| [[NSString stringWithUTF8String:temp_addr->ifa_nameisEqualToString:@"pdp_ip0"]) {

         // Get NSString from C String

         address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];

 } 


4. iOS 버전 관련

Android에서는 height에 %가 아닌 vh로 지정을 했었는데 iOS에서는 vh로 지정을 하면 iOS 9.0이상에서는 정상이였지만 iOS 8.0에서 헤더 부분의 투명한 처리가 되지 않는 현상도 있었습니다.


5. StatusBar Plugin

해당 플러그인을 사용하여 전체 화면을 StatusBar부분 밑으로 내려오게 하였으면 ionic _variables.scss에서 아래 부분을 수정하였습니다.

 $ios-statusbar-height:           0px !default;


현재 iframe과 관련된 부분이 모두 해결된 것은 아닙니다. 특정 화면에서 모바일 키보드가 올라오고 입력을 하면 화면이 올라 간다던지 특정 액션을 취하면 화면이 올라 간다던지 브라우저에서 나오지 않던 이상한 동작들이 생각보다 많습니다 또한 iOS에서는 4인치 이하 디바이스에서만 이상한 동작을 하는 현상도 발견되었습니다.


2015-12-23 수정

위의 현상과 관련되어 iframe의 높이와 관련된 현상이였습니다. iframe의 높이를 컨텐츠의 높이와 맞춰주기 위해서 onload이벤트에 아래 글을 참고하여 수정하시면 됩니다. 포인트는 높이에 대한 리셋이 되어야 새로운 페이지 접근 시에 스크롤이 가장 위에서 부터 시작합니다.

http://www.dyn-web.com/tutorials/iframes/height/


이번 프로젝트를 하면서 구조에 대한 중요성과 iframe은 쓰면 안된다는 확신이 생겼습니다. +_+


참고적으로 cordova ios 버전이 4.0이 얼마전에 나왔습니다. iOS UIWebView에서 WKWebView를 사용할 수 있다는 것이 핵심입니다.

http://cordova.apache.org/announcements/2015/12/08/cordova-ios-4.0.0.html



Posted by resisa
,

이전 글에서 User-Agent를 통해서 로그인 연동을 하였다고 하였습니다.

Cordova 문서를 살펴보면 아래와 같은 설정(config.xml)을 통해서 User-Agent를 전부 또는 추가할 수 있습니다.

http://cordova.apache.org/docs/en/edge/guide/platforms/android/config.html


<preference name="OverrideUserAgent" value="Mozilla/5.0 My Browser" />
<preference name="AppendUserAgent" value="Test/1.0" />

User-Agent:Mozilla/5.0 (Linux; Android 4.4.4; SHV-E370K Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36 Test/1.0


하지만 해당 설정은 기본 WebView에서만 사용할 수 있는 설정입니다.

Cordova 4.0부터는 Crosswalk WebView(Google Chromium)를 플러그인 형태로 추가할 수 있습니다. 안드로이드 WebView가 기본적으로 느리고 ionic에서 사용하는 몇몇 기능들 동작들이 제대로 안되는 문제도 있습니다. 다만 Crosswalk WebView를 추가하게 되면 앱 사이즈가 20MB정도 늘어납니다.

Crosswalk WebView에서 위의 설정을 사용할 수가 없어 Crosswalk WebView 소스에서 커밋된 리스트 중에 User-Agent를 설정하는 부분을 찾았습니다.

https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview/commit/0bea1d0313d05bfd74f02e52d2f85efdd7c48f4e


해당 commit위의 소스를 하나 더 적용시키면 중요한 소스는 아래와 같습니다.

private void initWebViewSettings() {

        webView.setVerticalScrollBarEnabled(false);

        String xwalkUserAgent = preferences == null ? "" : preferences.getString(PREF_USER_AGENT, "");

        if (!xwalkUserAgent.isEmpty()) {

            webView.setUserAgentString(xwalkUserAgent);

        }

    }

그런데 위의 소스는 Override되기만 하기 때문에 User-Agent에 들어가는 안드로이드 버전, 디바이스 정보 등등을 알수가 없습니다. 그래서 검색을 해보니 아래와 같이 변경을 하면 됩니다.

http://stackoverflow.com/questions/4103963/is-there-a-way-to-obtain-the-default-user-agent-string-aside-from-webview-getset


private void initWebViewSettings() {

        webView.setVerticalScrollBarEnabled(false);

        String xwalkUserAgent = preferences == null ? "" : preferences.getString(PREF_USER_AGENT, "");

        if (!xwalkUserAgent.isEmpty()) {

            String userAgent = System.getProperty( "http.agent" );

            webView.setUserAgentString(userAgent + " " + xwalkUserAgent);

        }

    }

User-Agent:Dalvik/1.6.0 (Linux; U; Android 4.4.4; SHV-E370K Build/KTU84P) Test/1.0


User-Agent 값을 비교해보시면 아시겠지만 브라우저 정보는 보이지 않습니다. 브라우저 정보는 Crosswalk WebView라 어떻게 보면 굳이 꼭 필요한가란 생각과 시간의 문제도 있고 이정도면 괜찮을 것 같습니다.


iOS에는 기본 WebView를 사용할 수도 있는데 아직 테스트는 해보지 않았습니다.


Posted by resisa
,

RSA를 이용하여 아이디와 패스워드를 암호화 하려고 하는데 클라이언트(javascript)에서 공개키로 암호화를 하고 서버(C#)에서 개인키로 복호화를 하는데 Bad Data 또는 포맷 예외가 발생하여 관련 내용을 검색해보았습니다.


http://stackoverflow.com/questions/19418021/rsa-encrypt-password-in-javascript-but-failed-to-decrypt-that-in-c-sharp


복호화가 실패하는 이유는 1. 둘 언어간에 byte order가 다르고 2. padding이 다르기 때문입니다.

위의 사이트에서는 해결 방법으로 Jocys JS.NET 라이브러리를 알려줍니다.

해당 라이브러리는 마치 C# 구문과 동일하게 javascript로 구현을 해놓았고 네임스페이스, 클래스명까지 동일하게 구현을 해놓았습니다. 우와~~~


http://www.jocys.com/Common/JsClasses/Documents/


// Create HMAC-MD5 Algorithm.
var hmac = new System.Security.Cryptography.HMACMD5(); 
// Convert string to array of bytes.
var key = System.Text.Encoding.UTF8.GetBytes("test key"); 
var data = System.Text.Encoding.UTF8.GetBytes("test data"); 
// Compute hash. 
var hashBytes = hmac.ComputeHash(key, data);
// Convert to HEX string.
var hex = System.BitConverter.ToString(hashBytes);
// Convert to GUID so you can store it inside database.
var guid = new System.Guid(hashBytes);


C#구문과 완벽하게 일치합니다.


실제로 RSA를 사용하여 암호화를 하니 발생하던 에러도 당연히! 사라졌습니다. 엄지척 ^^

Posted by resisa
,

CORS 문제 문제 문제

모바일 2015. 10. 29. 04:09

회사 일이 많아져서 정신없이 바쁘네요 ㅜㅜ

어떻게 하다보니 하이브리드앱에 모바일 웹을 넣는 조금은 변태적인 구조로 개발을 하게되었습니다.

거기다가 모바일 웹을 인앱브라우저 형태로 보여주기만 하는게 아니라 모바일웹처럼 보이는 하이드리드 앱이라고 해야하나 그런 모양새가 되어버렸습니다. 네이티브로 다 개발하기에는 이미 시간이 얼마 남지 않았고 앞으로 개발해야 하는 것도 눈에 밟히고;; 순수? 하이브리드로 하고 싶었지만 여러가지 이유로 무산 여튼 변태적인 구조속에서 골치가 아픈건 로그인 연동(모바일웹이 iframe으로 들어갔습니다)인데 크로스 도메인으로 인한 쿠키 공유가 되지 않아 무한 검색을 하다가 nodejs 플러그인(CorsProxy)을 찾았습니다.

https://www.npmjs.com/package/corsproxy


별도의 프록시 서버가 구동된다고 보면 되는데 사용 방법을 보면 아시겠지만 Prefix? 도메인명을 맞춰줘서 어떤 사이트라도 Sub도메인명이 되게 하여 해결하는 것입니다.(https도 지원)

http://localhost:1337/localhost:3000/sign_in

http://localhost:1337/my.domain.com/path/to/resource


다만 ionic framework+cordova로 만든 하이드리드 앱에 이걸 어떻게 넣어줘야하냐인데 방법이 없다는게 함정;;

무한 검색을 해보았지만 디바이스에서는 안된다는 글을 보고 nodejs 플러그인이 정말 많구나를 느끼며 낚시글을 마감합니다. ㅎㅎㅎ


아 그래서 쿠키를 포기하고 특정 User-Agent + 쿼리 스트링으로 연동하기로 했답니다;;;


크로스 도메인 관련 정리된 사이트는 아래걸 한번 보면 도움이 될듯 싶습니다.

http://www.slideshare.net/SlexAxton/breaking-the-cross-domain-barrier

Posted by resisa
,

작심 3일로 끝나버릴까봐 더 늦기 전에 다음 진도를 나가봅니다.

구성도는 아래와 같습니다.

  • FrontEnd
    • 이전에 글에 썼던 것처럼 nodejs 기반으로 뷰에 대한 렌더링이 클라이언트에서 된다고 생각하시면 됩니다.
    • Ionic Framework는 AngularJS와 Sass기반으로 구현되어 있습니다. Ionic을 단순하게 말하면 UI Framework라고 생각하시면 됩니다. CSS기반으로 작성된 앱스럽게 보이는 컨트롤 및 자바스크립트를 제공해준다고 생각하시면 됩니다.
    • 모바일 웹에서는 모바일 디바이스 API(디바이스 정보, 카메라 기타 등등)에 접근하는 것이 어렵지만(불가능?) 하이드리드 앱에서는 이러한 부분을 Apache Cordova를 통해서 가능하게 해줍니다. Cordova 디바이스 API는 플러그인 형태로 제공되며 추가적인 플러그인(PushPlugin 등등)도 Github에 공개되어 있습니다.
    • ngCordova는 AngularJS 컨트롤러에서 Cordova 디바이스 API를 사용하기 쉽게 확장해둔 라이브러리라고 생각하시면 됩니다.
    • Ionic Material은 Google Material 디자인처럼 Ionic 컨트롤을 확장한 라이브러리라고 생각하시면 됩니다.
      참고 https://www.materialup.com/
    • FrontEnd는 atom으로 코딩을 하고 gulp로 빌드를 해서 개발을 진행하고 있습니다.
      참고 : http://programmingsummaries.tistory.com/356

  • BackEnd
    • 서버는 Web API로 json 데이터를 제공해줍니다. Nancy로 구현하였는데 Nginx에도 올릴 수도 있다고 해서 다른 프로젝트에서 적용을 해보았는데 특별한 문제는 없고 IoC부터 라이팅 규칙 작성도 쉽고 직렬화 등 라이브러리 교체와 같은 부분들이 쉽게 되어 있습니다.
    • 보통 .NET에서는 직렬화 라이브러리로 JSON.NET를 많이 사용하는데 ServiceStack.Text(4.0이상은 유료입니다;;)가 성능이 제일 괜찮다고 하여 사용해보았습니다. 이전에 말한 것처럼 Nancy에서는 라이브러리 교체가 쉽게 때문에 문제가 생기면 바로 교체를 하려고 했었는데 크게 문제는 없었습니다.(json 데이터 파일을 읽다가 공백과 관련된 버그는 있었습니다.) 아 또 한가지 CSV와 JSV 다양한 데이터 포맷을 제공합니다. 동일 주소를 호출을 하여 다양한 포맷으로 데이터를 받을 경우(Content Negotiation)에 활용하면 괜찮습니다.
    • PushSharp은 GCM, Apns 기타 등등과 관련된 푸쉬 서비스에 대한 프로바이더입니다. GCM에 쏘는건 실제로 Http Post방식으로 쏘면 되는거라 어렵지 않아 굳이 쓸 필요까지는 없었는데 저는 오픈 소스를 사랑하기 때문에 ^^; 소스도 볼겸 사용해보았습니다.
    • SignalR은 실시간(양방향) 통신이 필요할 경우에 사용하려고 고려만 해둔 상태입니다.
      참고 : 
      초록색으로 표시한 부분은 직접 구현한 부분들이 아닌 외부와의 연동을 의미합니다.

  • GCM(Apns)
    • 푸쉬 서비스를 사용하는 이유는 앱이 실행중이지 않더라도 알림을 받으려고 하는 것입니다. 디바이스에서 먼저 GCM Service에 등록을 해줘야 하며 등록하는 과정에서 Token를 받습니다. 이러한 Token은 사용자별로 푸쉬 프로바이더로 전송을 해줘서 저장을 해줘야 합니다. 푸쉬 프로바이더에서는 저장된 Token을 사용하여 메세지를 GCM Service로 보냅니다. GCM Service에서는 받은 메세지를 디바이스로 보냅니다.
    • PushSharp에서는 GCM, Apns에 대한 구현이 각각 되어 있었는데 보통 아이폰은 Apns를 안드로이드폰은 GCM를 사용합니다. GCM 3.0부터는 등록 프로세스를 간소화하여 아이폰, 안드로이드폰, 크롬까지 지원을 합니다.
    • GCM를 사용하더라도 Apns에 인증을 하는 절차를 거쳐야 합니다. 실제로 GCM Service에서 Apns에 인증만 받는 건지 아니면 중간 역할을 해주는 건지는 확실하지 않지만 등록 프로세스를 간소화를 통하여 GCM의 Topic과 Device Group를 사용하기 위함입니다. 관련 내용은 슬라이드를 보시면 아실 수 있을 겁니다~ ^^

아직까지도 보여줄 코딩한줄이 없네요 ㅎㅎ;; 블로그질을 오랫만에 하니 시간을 은근히 잡아먹네요 ㅜㅜ
다음에는 GCM 3.0의 등록 프로세스 간소화를 위한 iOS와 관련된 설정 및 코드로.........


Posted by resisa
,

오랫만에 블로그에 글을 쓰네요 ^^;

블로그질을 다시 시작해보는 이유는 회사에서 제목처럼 하이브리드 앱 개발을 시작한게 동기가 되어서 입니다. 몇 개의 글을 쓸지는 모르겠지만요.. ㅎㅎ


스터디 모임으로 iOS, Android 개발을 해보았는데 생각보다 진행이 잘 되지 않았습니다. 여러가지 이유가 있겠지만 아무래도 경험이 없었던게 걸림돌이 되지 않았나 싶습니다. 그래서 눈을 돌려 하이브리드 앱 개발을 해보았습니다.

아래는 회사에서 발표한 내용으로 개요나 앞으로의 계획 등등을 제외한 발표 자료입니다.

http://slides.com/sungkikim/deck#/


결론적으로 Ionic Framework와 Apache Cordova를 사용하여 프로토타입 앱을 만들었습니다.

React Native, Native Script도 Ionic과 마찬가지로 프론트엔드 즉 배포되는 앱에 nodejs를 기반으로 마치 클라이언트에 웹서버가 있는 것처럼 구동되는 방식입니다.

실제로는 Ionic으로 먼저 개발이 어느정도 되고 나서 다른 것을 알게 되었는데 다른 것을 알았다고 하더라도 Ionic을 선택하지 않았을까 싶습니다. angularjs도 한번 해보고 싶었고 html5 + javascript의 조합이 지식활용도 면에서도 좋은 선택이지 않았나 싶습니다.

처음부터 너무 무리하면 안될 것 같아 첫 번째 글은 여기서 마치고 다음 글은 구성도에 대한 이야기를 더 자세히 해볼 예정입니다.


P.S 블로그 관리를 안하다보니 한달 전부터인가 방명록에 일본어로 된 글들이 엄청 많아서 모두 삭제했는데 그때부터 방문자가 늘었는데 줄지 않고 있네요 ㅎㅎㅎ;; 의미없는 숫자이지만 블로그질을 하라는 하늘이 계시?? ㅎㅎ

Posted by resisa
,