폰갭 기반 하이브리드 모바일 애플리케이션 개발

[출처] http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=37755

대표적인 하이브리드 앱 개발 플랫폼인 폰갭(PhoneGap)에 대해 살펴보자. 폰갭은 비동기 방식의 다양한 자바스크립트 API를 제공하는 하이브리드 플랫폼으로서 iOS, 안드로이드, 블랙베리, HP 웹OS, 윈도우폰7, 심비안 등 다양한 스마트폰을 지원한다.

최재규 magicsoft7@gmail.com|스마트TV, 스마트칩, 스마트폰 그리고 하이브 리드 플랫폼을 거쳐 현재는 삼성SDS에서 클라우드 기반 PaaS 시스템을 개발하고 있다. 최신 소프트웨어 관련 기술에 관심이 많고 자료 정리 및 공유를 좋아한다. 그런 습관으로 10년 전 첫 번째 책을 쓰기 시작한 이후로 현재 열 번째 책을 쓰고 있다. 요즘은 다양한 주제에 대해 사람들과 의견을 공유할 수 있는 SNS 활동에 관심이 많으며, 여행과 나지막한 산에 오르는 것을 즐기는 개발자다.

폰갭의 구조와 특징을 파악하고 폰갭을 활용한 하이브리드 앱 개발 방법도 알아보자.

폰갭
현재 폰갭은 하이브리 앱 개발 방식을 주도하고 있다. www. phonegap.com을 방문하면 폰갭에 대한 다양한 정보를 얻을 수 있다. 이 사이트는 폰갭 라이브러리와 개발자 문서 등 폰갭 기반의 하이브리드 앱 개발에 필요한 모든 정보를 제공한다.

폰갭은 MIT 라이선스를 갖는 오픈소스로 개발 중이며, https: //github.com/phonegap/phonegap를 통해 폰갭 소스코드를 얻는 것도 가능하다.

폰갭이 채택한 MIT 라이선스는 가장 느슨한 오픈소스 라이선스로 코드의 수정·배포가 자유롭다. 이러한 라이선스 정책으로 인해 최근 많은 기업이나 단체에서 폰갭 기반의 하이브리드 앱 개발 과제를 진행하거나 검토하고 있다.

<그림 1> 폰갭 개발 절차

폰갭을 이용해 하이브리드 앱을 만들고 사용하려면 다음과 같은 절차를 따라야 한다. 우선 웹 표준을 준수하는 HTML5, CSS, 자바스크립트로 사용자 앱을 개발해야 한다. 이렇게 만들어진 앱은 폰갭 라이브러리를 이용해 패키징하고, 웹킷(Webkit)이 제공되는 브라우저가 탑재된 단말에 배포돼 실행된다.

폰갭을 사용하면 스마트폰의 가속도, 카메라, 주소록, 파일, GPS, 미디어, 소리, 진동, 스토리지 등의 다양한 네이티브 기능을 사용할 수 있다.

<표 1> 폰갭 API 종류

하이브리드 앱을 개발하기 위해서는 HTML, CSS, 자바스크립트를 쉽게 처리할 수 있는 맥 OS의 대시코드(Dashcode)나 웹 에디터 도구를 사용해 개발 생산성을 높이는 것이 좋다. 물론 텍스터 에디터로 개발해도 된다. HTML, CSS, 자바스크립트로 개발된 하이브리드 앱은 스마트폰 플랫폼별로 빌드해야 한다. iOS일 경우에는 Xcode를 이용해 Phongap.lib 파일을 실행파일에 포함시켜 패키징해야하며, 안드로이드는 이클립스와 안드로이드 SDK 개발환경을 사용해 패키징해야 한다. 즉, 모든 플랫폼에 공통으로 적용되는 패키징 시스템이 있는 것이 아니라 스마트폰 플랫폼에 따라 폰갭 라이브러리를 개별적으로 패키징하는 구조다.

폰갭 기반 하이브리드 앱 개발
폰갭 기반 하이브리드 앱을 개발해 보자. 우선 폰갭 개발환경을 만든다. 폰갭 개발환경 설정은 http://www.phonegap.com/ start에서 확인할 수 있다. iOS, 안드로이드, 블랙베리, 웹 OS, 심비안 등의 다양한 플랫폼을 제공한다.

<화면 1> 폰갭 개발환경 설정

현재 가장 대중적인 스마트폰 개발환경은 애플의 Xcode와 구글의 안드로이드 SDK다. 두 가지 개발환경에 폰갭을 설치하고 간단한 샘플 프로그램을 작성해 보자.

iOS에서 폰갭 첫 번째 프로그램 개발
iOS용 폰갭을 설치하려면 맥 OS X 스노우 레오파드 10.6 이상의 OS가 설치된 매킨토시가 필요하다. 또한 iOS 디바이스(아이폰, 아이패드, 아이팟 터치)와 iOS 개발자 라이선스도 필수다. 만약 실제 디바이스가 아닌 시뮬레이터 상에서 개발만 하고 싶다면 iOS 개발자 라이선스는 없어도 된다. 애플 개발자 포털에서 Xcode를 다운받아 설치하면 iOS에서 폰갭으로 개발할 수 있는 모든 준비는 마무리된다.

<화면 2> 폰갭 프로젝트 설정

이제 폰갭 라이브러리 파일을 다운받아 iOS 안에 Phone GapLibInstaller.pkg 파일을 설치한다. iOS용 폰갭 설치와 관련된 내용은 http://www.phonegap.com/start#ios를 참고하자.

폰갭 설치 후 Xcode를 실행하고 [New Project...]를 선택하면 <화면 2>와 같은 폰갭 템플릿 화면이 출력된다. [PhoneGap-based Application] 아이콘을 선택해 새 프로젝트를 생성한다. 프로젝트 이름은 FirstPhoneGap으로 설정한다.

FirstPhoneGap 프로젝트에는 www 디렉터리가 있는데 이 디렉터리 아래 index.html을 선택하고 <리스트 1>을 입력해 보자.

<리스트 1> index.html

1: <html>
2: <head>
3: <title>Device Properties Example</title>
4: <script type="text/__javascript" charset="utf-8" src="phonegap.js"></script>
5: <script type="text/__javascript" charset="utf-8">
6: function onLoad() {
7:  document.addEventListener("deviceready", onDeviceReady, false);
8: }
9: function onDeviceReady() {
10: var element =  document.getElementById('deviceProperties');
11: element.innerHTML = 'Device Name:' + device.name +'<br/>' +
12: 'Device PhoneGap:' + device.phonegap +'<br/>' +
13: 'Device Platform:' + device.platform + '<br/>' +
14: 'Device UUID:' + device.uuid + '<br/>' +
15: 'Device Version:' + device.version + '<br/>';
16: }
17: </script>
18: </head>
19: <body onload="onLoad()">
20: <p id="deviceProperties">Loading device properties...</p>
21: </body>
22: </html>


Xcode의 [Build and Run] 메뉴를 선택해 작성된 코드를 컴파일한다. Xcode는 빌드가 끝나면 <화면 3>처럼 시뮬레이터에 디바이스 정보를 출력한다.

<화면 3> 폰갭 프로젝트 설정

<script type=“text/__javascript” charset=“utf-8” src=“phonegap.js”>

<리스트 1>의 index.html 파일 네 번째 라인에서 phonegap.js 스크립트 파일을 읽어들인다. 이 파일은 폰갭 API를 사용해 하이브리드 앱 작성에 필요한 기본 정보를 갖고 있다. index.html 파일에는 onLoad(), onDeviceReady() 자바스크립트 함수가 정의돼 있다. onLoad() 함수는 19라인의 HTML Body 태그에서 페이지 로드와 함께 호출된다. onDeviceReady() 함수는 onLoad() 함수 안에서 호출된다. 즉, 하이브리드 앱이 시작되면 Webkit 엔진이 index.html 파일을 읽어들여 작동을 시작한다. 이때 Body 태그의 onload 속성으로 지정된 onLoad() 함수가 실행된다. 결과적으로 11~15라인에 정의된 디바이스 정보가 출력된다.

<표 2> 디바이스 프로퍼티 정보

안드로이드 플랫폼에서 작동하는 폰갭 샘플 살펴보기
www.phonegap.com에서 다운받은 폰갭 설치파일(phonegap -0.9.5.1.zip)의 압축을 풀면 Android 폴더 아래에 <리스트 2>, <화면 4>와 같은 폰갭 샘플 프로그램이 있다. 이 예제는 가속도, GPS, 전화걸기, 소리, 진동, 사진 및 연락처 정보 얻기 등을 어떻게 처리하는지 상세히 소개한다.

<리스트 2> 폰갭 샘플 코드

... 생략 ...
import com.phonegap.*;
... 생략 ...
public class exampleapp extends DroidGap { // DroidGap 상속받은
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 초기 로딩 파일 지정
super.loadUrl("file:///android_asset/www/index. html");
}
} ... 생략 ...

<화면 4> 폰갭 샘플

안드로이드 폰갭 개발환경 설정은 http://www.phonegap. com/start#android를 참고하자. 안드로이드 환경에서는 DroidGap 클래스를 상속받은 클래스의 onCreate 메소드가 하이브리드 앱의 엔트리 포인터가 된다. 폰갭은 onCreate의 loadUrl 메소드에 정의된 파일을 웹킷에 올려 하이브리드 기능을 시작한다. loadUrl에 asset/www/index.html을 지정했으므로 하이브리드 앱은 index.html을 찾아 작동을 시작한다. www 디렉터리 아래에는 index.html, main.js, master.css, phonegap. 0.9.5.1.js 파일이 있는데 엔트리 포인트인 index.html의 HTML 태그를 보면 다음과 같다.

<script type=“text/__javascript” charset=“utf-8” src=“phonegap.0.9.5.1.js”></script>
< script type=“text/__javascript” charset=“utf-8” src=“main.js”></script>

이처럼 폰갭과 자바스크립트가 정의된 외부 파일을 포함시키는 부분이 정의돼 있다.

폰갭 샘플은 폰갭이 제공하는 상당수의 API를 사용하고 있다. 예를 들어 예제의 [Vibration] 버튼은 폰갭 API를 이용해 진동효과를 처리하는 방법을 소개한다. 다음과 같이 버튼을 클릭하면 main.js 파일의 vibrate 함수가 호출된다. 함수 안에는 폰갭이 제공하는 vibrate 함수가 설정돼 있다.

var vibrate = function() {
navigator.notification.vibrate(0);
// 지정된 시간만큼 진동모터를 작동한다.
};

http://docs.phonegap.com/phonegap_notification_notification.md.html#notification.vibrate를 참고하면 폰갭 vibrate 함수의 상세정보를 확인할 수 있다.

폰갭 API는 비동기 자바스크립트로 구현돼 있다. 즉, 원하는 값을 곧바로 받을 수 없고 콜백함수를 등록해 놓으면 특정 시점에 값이 반환되는 구조다. 물론 Device와 같은 특정 API는 폰갭 초기 로딩 시 값을 설정해 원하는 시점에 사용하는 것도 가능하다.

getCurrentPosition 함수는 디바이스의 GPS 모듈정보를 얻어오는 기능을 처리한다. 일반적으로 디바이스의 GPS 정보는 실시간으로 얻어올 수 없다. 따라서 getCurrentPosition는 콜백함수 등록 방식을 통해 디바이스가 GPS 정보를 올려주면 그 시점에 해당 값을 사용할 수 있도록 구성돼 있다. getCurrentPosition 함수는 다음과 같이 세 개의 인자를 갖고 있다. 첫 번째 인자는 성공할 경우 실행할 콜백함수, 두 번째 인자는 GPS 값 획득에 실패할 경우 실행할 함수를 지정할 때 사용한다. 마지막 인자는 GPS 정보를 얻기 위한 세부 옵션을 지정하는 용도로 사용된다.

navigator.geolocation.getCurrentPosition(geolocationSuccess,
[geolocationError],
[geolocationOptions]);

GPS를 사용해 위치정보를 처리하는 [Get Location] 버튼은 main.js 파일의 getLocation 함수를 호출한다. 이 함수도 폰갭이 제공하는 geolocation 기능을 사용해 구현됐다.

<리스트 3> geolocation 기능

1: var getLocation = function() {
2: var suc = function(p) {
3: alert;(p.coords.latitude + " " + p.coords.longitude);
4: };
5: var locFail = function() {
6: }; // getCurrentPosition은 세 개의 인자를 갖지만 마지막 인자는 생략됐음.
7: navigator.geolocation.getCurrentPosition(suc, locFail); // 폰갭 위치 정보 API
8: };


일반적으로 폰갭이 제공하는 비동기 API들은 다음 형태를 띠고 있다.

[function]( [SuccessCallback], [ErrorCallback], [Options] );

따라서 폰갭을 이용해 특정 기능을 구현할 때는 해당 기능을 처리하는 함수([function])를 선언하고, 성공했을 때 수행할 함수([SuccessCallback]), 실패할 경우 수행할 함수([Error Callback])를 지정해야 한다. 이때 [Options]은 JSON 형태로 다양한 값을 지정할 수 있다.

getLocation 함수는 일곱 번째 줄에서 폰갭의 getCurrent Position 함수를 호출한다. 첫 번째 인자인 suc는 2~4라인에 선언돼 있다. 두 번째 인자인 locFail은 5~6라인에 선언돼 있다. 폰갭 getCurrentPosition 함수는 GPS 정보를 얻으면 2~4라인의 suc 함수를 호출한다. 만약 일정 시간동안 GPS 정보를 얻지 못한다면 5~6라인의 locFail 함수를 호출한다. GPS 정보를 성공적으로 얻어 2~4라인이 실행되면 폰갭은 suc 함수의 매개변수로 p 값을 넘겨준다. 이 값에는 coords와 timestamp 속성이 들어있으며, 이 속성값을 통해 다양한 GPS 정보(Latitude, Longitude, Altitude, Accuracy, Altitude Accuracy, Heading, Speed, Timestamp)를 얻을 수 있다.

만약 폰갭을 이용해 스마트폰의 연락처에서 특정 정보를 얻고 싶다면 어떻게 해야 할까? 우선 폰갭 API 중에서 연락처를 검색하는 함수가 무엇인지 파악해야 한다. 연락처는 Contacts에 정의된 contacts.create와 contacts.find를 이용해 처리할 수 있다. 특정 정보를 얻고 싶다면 contacts.find 함수를 사용하면 된다. 이 내용은 다음 URL에서 확인할 수 있다.

http://docs.phonegap.com/phonegap_contacts_contacts.md.html#contacts.find

find 함수는 네 개의 인자를 갖는다.

첫 번째 인자는 찾고자 하는 연락처의 정보를 담고 있는 매개변수다. 두 번째 인자는 해당 정보를 찾았을 경우 실행될 콜백함수이며, 세 번째 인자는 해당 정보를 찾지 못했을 경우 호출될 콜백함수다. 마지막 인자는 연락처 검색 옵션을 지정하는 데 사용된다.

navigator.service.contacts.find(contactFields, contactSuccess, contactError, contactFindOptions);

다음의 get_contacts 함수는 find 함수를 이용해 연락처 정보를 가져오는 기능을 처리한다.

<리스트 4> get_contacts 함수

1: function contacts_success(contacts) {
2: alert;(contacts.length + ' contacts returned.' + (contacts[2] &&
3: contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted) : ''));
4: }
5: function get_contacts() {
6: var obj = new ContactFindOptions();
7: obj.filter = "";
8: obj.multiple = true;
9: obj.limit = 5;
10: navigator.service.contacts.find(
11: [ "displayName", "name" ], contacts_success,
12: fail, obj);
13: }


get_contacts 함수는 열 번째 줄에서 contacts.find 함수를 호출한다. 첫 번째 인자로 지정된 [“displayName”, “name”]은 연락처에서 표시 이름, 이름으로 필터링해 값을 찾으라는 뜻이다. 두 번째 인자는 연락처 검색이 성공할 경우 1~4라인에 정의된 contacts_success 함수를 실행하도록 설정돼 있다. 6~9라인에서 검색 옵션을 설정한다.

샘플예제는 폰갭이 제공하는 다양한 API를 사용해 작성됐다. API에 대한 자세한 내용은 http://docs.phonegap.com/를 참고하자. 이곳에는 API에 대한 상세한 설명과 예제가 풍부하다. 폰갭은 비동기 방식의 자바스크립트 API 셋이며, 그 사용방법이 쉽고 명쾌하다. 따라서 몇몇 예제와 API 문서를 잘 활용하면 빠르고 쉽게 학습할 수 있다.

<화면 5> 폰갭 API 레퍼런스

하이브리드 앱 개발 방식 정리
이렇게 만들어 본 하이브리드 앱 개발 방식을 정리해 보자.

1. 개발 플랫폼에 맞는 개발환경을 설치한다(www.phonegap. com/start).
2. www 디렉터리 아래에 index.html 파일을 생성하고 phonegap.jsp 스크립트 파일을 추가한다<script type=“text/__javascript” charset=“utf-8” src=“phonegap.js”>.
3. 하이브리드 앱의 주요 로직을 자바스크립트 모듈로 작성한다. 이때Native 기능 호출과 관련된 기능은 폰갭 API를 사용한다.
4. HTML, CSS, JQuery 등의 다양한 웹 UI 기술을 사용해 화면 UI를 구성한다.
5. 시뮬레이터나 디바이스에서 하이브리드 앱을 실행하며 디버깅한다.

폰갭 하이브리드 앱의 기본 컨셉트는 하나의 소스(www 디렉터리 아래에 있는 파일들)로 다양한 스마트폰에서 소스코드의 수정 없이 곧바로 실행하는 것이다. 하지만 스마트폰 플랫폼의 차이로 인해 폰갭이 제공하는 API의 지원범위가 제한된다. 따라서 하이브리드 앱 개발 시에는 API 호환성에 대한 세심한 배려가 필요하다.

<표 3> 폰갭 API 지원 범위

폰갭을 사용하면 Objective-C, 자바에 대한 지식이 없어도 해당 플랫폼에 맞는 프로그램을 작성할 수 있다. 폰갭이 제공하는 API는 모두 자바스크립트 형태이므로 하이브리드 앱 개발자는 www 디렉터리 아래에 HTML, CSS, 자바스크립트를 사용해 프로그램을 작성하면 된다.

디버깅 및 배포
폰갭으로 개발한 하이브리드 앱은 네이티브와 웹이 혼합된 형태다. 하이브리드 앱은 플랫폼에 따라 디버깅 방법이 다르다. 조금 더 엄밀히 말하면 하이브리드의 네이티브 부분은 해당 플랫폼(iOS, 안드로이드)에서 제공되는 방식으로 디버깅한다. 하지만 하이브리드 웹 부분은 플랫폼에 상관없이 HTML의 디버깅 방식을 따르면 된다.

폰갭 하이브리드 네이티브 부분은 사실 수정이나 디버깅이 필요 없다. 대부분의 디버깅 이슈는 웹 부분에서 일어난다. 다음 호부터는 폰갭 네이티브 코드를 변경해 새로운 사용자 정의 API를 추가하는 방법을 알아볼 것이다. 네이티브 디버깅은 그때 살펴보자.

웹 부분 디버깅은 주로 로그와 Alert 메시지를 통해 이뤄진다. HTML이나 CSS는 주로 UI 표현에 주력하므로 동적인 값의 변화가 적은 편이다. 이같은 부분은 로그나 Alert 메시지 창을 통해 작업해도 된다. 하지만 기능을 처리하는 자바스크립트는 디버깅이 쉽지 않다. 특히 작성한 자바스크립트 코드가 유효한지 검증하는 일도 쉬운 일이 아니다. 이럴 경우 사용할 수 있는 유용한 도구 몇 가지를 소개한다.

· JList.com : 자바스크립트 코드의 유효성을 검증해 주는 화면을 제공한다(www.jslint.com).
· JSHint.com : JList.com처럼 사용자가 작성한 코드의 유효성을 검증해 준다(www.jshint.com).
· SproutCore Bundle for TextMate : 애플이 제공한 JavaScriptCore. framework 라이브러리를 바탕으로 JSLint 스크립트를 수행해 코드의 유효성을 검증한다(https://github.com/sproutit/sproutcore-tmbundle).

원격으로 웹을 검색할 수 있는 디버거로 Weinre(https:// github.com/pmuellr/weinre)가 있다. Weinre를 사용하면 원격지 환경에서 하이브리드 앱의 값을 검사할 수 있다. 자세한 사용법은 http://pmuellr.github.com/weinre/을 참고하자.

<화면 6> Weinre를 사용한 디버깅

폰갭으로 개발된 하이브리드 앱의 배포는 www 디렉터리 아래의 파일들을 어디에 위치시킬 것인지에 따라 세 가지 타입으로 구분할 수 있다.

1. www 디렉터리 아래 파일들을 해당 플랫폼의 환경에 맞게 패키징해 폰 내부에 저장
2. www 디렉터리 아래 파일들을 원격지에 배치
3. www 디렉터리 아래 일부 파일은 폰 내부에, 나머지 파일은 원격지에 배치

B2B에서 가장 일반적으로 사용되는 방식은 폰 내부와 원격지에 분산배치시키는 것이다. 디바이스와 연관된 기능은 폰 내부에 배치하고 서버와의 통신이나 정보의 변경이 잦은 페이지는 원격지 서버에 배치한다.

다음 호에서는 폰갭에 사용자 정의 API를 추가하는 방법에 대해 살펴볼 것이다. 또한 폰갭 네이티브 코드의 작동 흐름과 웹킷 컴포넌트 제어에 대해서도 자세히 소개한다.

참고자료
1. http://www.phonegap.com
2. http://www.phonegap.com/2011/05/18/debugging-phonegap-__javascript/
3. http://building-iphone-apps.labs.oreilly.com/

+ Recent posts