17.알람 앱 개발

Edit

이번 장에서는 앞서 설명한 앱 개발 절차에 따라 알람 기능을 수행하는 간단한 데모 앱을 제작해보며 실제 사례에서 어떻게 활용될 수 있는지 살펴봅니다.

『UI 디자인과 이벤트 처리』, 『배포 파일 생성』, 『UI만 포함된 앱 빌드』, 『안드로이드 프로젝트 내보내기(Export)』, 『알람 기능 구현』, 『안드로이드 프로젝트 들여오기(Import)』, 『전체 앱 빌드』에서는 넥사크로 스튜디오, 앱 빌더, 안드로이드 스튜디오를 사용해서 앱을 제작하는 과정을 다룹니다. 그리고 『앱 설치 및 실행』에서는 완성된 앱을 직접 스마트폰에 설치하여 구현한 기능이 잘 동작하는지 확인합니다.

『앱 데모 및 소스코드』에서는 완성된 앱 데모와 소스 코드 링크를 제공합니다.

마지막으로 『참고』에서는 알람 앱 개발 단계에서 참고할 수 있는 설명을 제공합니다.

알람 앱의 구조와 주요 구성요소

일반적으로 알람은 사용자가 시간을 설정하면 설정된 시간에 알람음을 재생하여 사용자에게 알려주는 기능을 합니다. 앱 개발에 앞서 알람 앱의 구조와 기능 구현을 위해 어떤 요소들이 필요한지 살펴봅니다.

알람 앱의 주요 구성 요소로는 알람을 설정하는 넥사크로플랫폼 디자인 화면, 디자인 화면을 화면에 출력해주는 NexacroActivity, 알람을 관리하는 AlarmManager, 알람 이벤트를 수신하는 브로드캐스트 리시버, 알람음을 재생하는 미디어 플레이어 서비스 그리고 자바스크립트와 자바 객체간의 메시지 전달을 담당하는 UserNotify 이벤트 객체, callScript 메소드 등이 있습니다.

그림 17-1앱_구성_요소_20190819

구성 요소

설명

1 NexacroActivity

넥사크로플랫폼 17 프레임워크와 넥사크로 스튜디오에서 구현한 애플리케이션을 로딩하여 화면에 출력합니다. 앱이 실행되면 먼저 NexacroUpdatorActivity가 실행되어 업데이트 체크를 수행한 후 NexacroActivity를 실행해 줍니다.

2 UserNotify

이벤트 클래스로 자바스크립트에서 자바로의 인터페이스를 제공합니다. 자바스크립트에서 ID, 문자열 인수와 함께 userNotify 메소드를 호출하면 자바에서 onUserNotify 이벤트 함수를 통해 인수값을 받을 수 있습니다.

3 AlarmManager

시스템의 알람 서비스로 접근할 수 있는 기능을 제공합니다. 이를 통해 알람 설정/해지합니다.

4 BroadcastReceiver

안드로이드 시스템에서 알람 발생시 전달되는 브로드캐스트를 수신하는 역할을 수행합니다.

5 Service

알람 발생시 백그라운드에서 MediaPlayer를 사용해 알람음을 재생합니다.

17.1UI 디자인과 이벤트 처리

넥사크로 스튜디오를 사용하여 UI를 디자인하고 사용자 액션으로 발생하는 이벤트에 대한 처리 로직을 구현합니다.

알람 앱은 하나의 폼과 날짜, 시간을 설정할 수 있도록 몇 개의 컴포넌트로 구성되는 간단한 구조를 가집니다. 주요 컴포넌트로는 날짜를 선택하기 위한 Calendar 컴포넌트, 시간을 설정하기 위한 Spin 컴포넌트 그리고 기능을 동작시킬 Button 컴포넌트가 있습니다. 다음은 우리가 제작할 알람 앱의 화면을 구성하는 컴포넌트 배치와 버튼의 기능을 정리한 표입니다.

그림 17-2앱 화면

기능

설명

Alarm SET

현재 설정된 시간으로 알람을 설정합니다.

Alarm OFF

알람을(알람음 재생) 종료시킵니다.

Alarm CANCEL

현재 설정된 알람을 취소합니다.

Alarm CHECK

현재 알람이 설정되어 있는지 확인합니다.

17.1.1넥사크로 프로젝트 생성

새로운 넥사크로 프로젝트를 생성하고 스마트폰 환경에 맞게 스크린 및 프레임을 설정합니다.

1

메뉴에서 [File > New > Project]를 실행하여 프로젝트 위저드를 실행합니다.

2

프로젝트 위저드 Screen 설정 화면에서 Phone을 선택한 후 Next 버튼을 클릭합니다.

그림 17-3nexacrostudio_프로젝트생성시_01_phone_screen_설정화면_01_720

3

프로젝트 위저드 Frame 설정 화면에서 프레임 옵션을 설정합니다.

Frameset Template 탭에서 "Full"을 선택합니다.

그림 17-4nexacrostudio_projectwizard_frame설정_01

Details Setting (Optional) 탭에서 Mainframe 설정의 Show Titlebar와 Show Statusbar 사용을 체크 해제합니다. PC 환경과 달리 모바일 환경에서는 일반적으로 화면 크기에 제약을 받기 때문에 Titlebar나 Statusbar와 같이 실행에 꼭 필요한 요소가 아니면 제거하는게 좋습니다.

그림 17-5nexacrostudio_projectwizard_frame설정_02

4

Finish 버튼을 클릭하여 프로젝트 생성을 완료합니다.

17.1.2테마 적용

기본으로 적용되는 default 테마를 안드로이드 환경에 최적화된 android 테마로 변경합니다.

1

Resource Explorer에서 NexacroTheme의 android.xtheme을 선택한 후 아래 Theme로 드래그 앤 드랍으로 테마를 추가합니다. 기존의 default 테마는 필요없으므로 삭제합니다.

그림 17-6nexacrostudio_테마변경_01

2

Project Explorer에서 Environment를 선택한 후 Properties 창에서 themeid 속성을 android로 설정하여 테마를 적용합니다.

그림 17-7nexacrostudio_테마변경_02

17.1.3폼 생성

1

메뉴에서 [File > New > Form (.xfdl)]를 클릭하여 폼 위저드를 실행한 후 폼 이름을 "AlarmSet"으로 설정하고 screenid 속성을 "Phone_screen"으로 설정합니다.

그림 17-8nexacrostudio_폼생성_01

screenid 속성값을 설정하면 위 그림과 같이 현재 폼의 크기가 설정한 스크린에 맞춰 변경된다는 정보창이 열립니다. YES를 선택하여 폼을 생성합니다. YES를 선택하면 폼 크기가 480*768로 변경되고 NO를 선택하면 기본 크기인 1280*720이 유지됩니다.

Screen ID 값인 "Phone_screen"은 폰 스크린 생성시 기본으로 설정되는 값입니다. 만약 스크린 생성시 다른 이름을 설정했다면 새로 설정한 이름이 표시됩니다.


폰 스크린은 모바일 화면에 적합하도록 사전에 mobile_small(480*768), mobile_medium(640*1024), mobile_large(800*1280)의 세 가지 타입으로 정의되어 있습니다. 여기서 기본으로 적용되는 타입이 mobile_small입니다. 폰 스크린의 타입을 변경하려면 Screen의 type 속성에서 설정할 수 있습니다.

2

Project Explorer에서 WorkFrame(ChildFrame)을 선택한 후 Properties 창에서 formurl 속성값을 "Base::AlarmSet.xfdl"로 설정합니다.

그림 17-9nexacrostudio_폼생성_03

17.1.4컴포넌트 생성 및 배치

각 컴포넌트를 다음 앱 화면과 같이 생성, 배치하고 속성을 설정합니다.

그림 17-10앱 화면

1

Calendar 컴포넌트를 생성하고 Properties 창에서 속성을 다음 표와 같이 설정합니다.

컴포넌트

속성

Calendar

popuptype

center

type

monthonly

usetrailingday

true

2

Spin 컴포넌트를 2개 생성하고 Properties 창에서 속성을 다음 표와 같이 설정합니다.

컴포넌트

속성

Spin(시간 설정)

id

spn_hour

circulation

true

max

23

min

0

font

normal 32pt/normal "Arial"

Spin(분 설정)

id

spn_min

circulation

true

max

59

min

0

font

normal 32pt/normal "Arial"

3

나머지 화면을 구성하는데 필요한 Static, Button 컴포넌트를 생성하여 Properties 창에서 id 속성을 다음 표와 같이 설정합니다.

컴포넌트

속성

Static(제목)

id

stt_subject

text

Wake me up before I'm late

Static(Hour)

id

stt_hour

text

Hour

Static(Min)

id

stt_min

text

Min

Button(Alarm SET)

id

btn_setAlarm

text

Alarm SET

Button(Alarm OFF)

id

btn_offAlarm

text

Alarm OFF

Button(Alarm CANCEL)

id

btn_cancelAlarm

text

Alarm CANCEL

Button(Alarm CHECK)

id

btn_checkAlarm

text

Alarm CHECK

17.1.5이벤트 처리

1

전연 변수를 선언합니다.

폼에서 자유롭게 userNotify 메소드를 사용하기 위해 xfdl의 가장 앞에 선언합니다

/* AlarmSet.xfdl */
var objEnv = nexacro.getEnvironment();

2

Form의 onload 이벤트 함수에서 컴포넌트의 초기값을 설정합니다.

폼 로딩시 현재 시간을 구하여 Calendar와 Spin 컴포넌트에 설정합니다.

/* AlarmSet.xfdl */
this.AlarmSet_onload = function(obj:nexacro.Form,e:nexacro.LoadEventInfo)
{
    var dTime = new Date();    
    
    var strDate = dTime.getFullYear() + (dTime.getMonth()+1).toString().padLeft(2, "0") + dTime.getDate().toString().padLeft(2, "0");
    
    this.Calendar00.set_value(strDate);
    
    this.spn_hour.set_value(dTime.getHours().toString().padLeft(2, "0"));
    this.spn_min.set_value((dTime.getMinutes()).toString().padLeft(2, "0"));
};

3

Alarm SET(알람 설정) 버튼의 터치 이벤트 함수를 작성합니다.

사용자가 Calendar와 Spin 컴포넌트로 날짜와 시간, 분을 설정하면 그 시간값을 문자열 변수에 담아 userNotify 메소드로 자바 코드로 전달합니다.

/* AlarmSet.xfdl */
this.btn_setAlarm_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo)
{
    var strYear = this.Calendar00.getYear();
    var strMonth = this.Calendar00.getMonth();
    var strDay = this.Calendar00.getDay();
    var strHour = this.spn_hour.value;
    var strMin = this.spn_min.value;
    
    var strParams = strYear +","
        + strMonth +","
        + strDay +","
        + strHour +","
        + strMin;
    
    objEnv.userNotify(101, strParams);    
    trace(strParams);
};

userNotify 메소드 호출시 사용하는 ID 값과 문자열은 자바 코드에서 사용자가 어떤 명령을 내렸는지 구분하는데 사용되는 식별자와 상태값입니다. 자바스크립트 코드에서 userNotify 메소드를 호출하면 자바 코드에서 이벤트(onUserNotify)가 발생하며 ID값과 문자열을 이벤트 함수의 매개변수로 전달 받을 수 있습니다. 다음은 userNotify 메소드 호출시 사용하는 ID와 상태값을 정리한 표입니다.

코드값

상태값

설명

101

시간 정보

알람 설정 처리

102

"ALARMOFF"

알람 중지 처리

103

"ALARMCANCEL"

설정된 알람을 취소

104

"ALARMCHECK"

설정된 알람이 있는지 확인

901

"ACTIVITYHIDE"

앱 종료

4

Alarm OFF(알람 중지), Alarm CANCEL(알람 취소), Alarm CHECK(알람 확인) 버튼의 터치 이벤트 함수를 작성합니다.

Alarm OFF 버튼은 알람 발생시 알람음이 재생되는데 이를 종료시킵니다.

/* AlarmSet.xfdl */
this.btn_offAlarm_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo)
{
    var strParams = "ALARMOFF";    
    
    objEnv.userNotify(102, strParams);
    trace(strParams);
};

Alarm CANCEL 버튼은 알람이 설정되어 있을 때 이를 취소합니다.

/* AlarmSet.xfdl */
this.btn_cancelAlarm_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo)
{
    var strParams = "ALARMCANCEL";
    
    objEnv.userNotify(103, strParams);
    trace(strParams);
};

Alarm CHECK 버튼은 시스템에 설정된 알람이 있는지 확인합니다.

/* AlarmSet.xfdl */
this.btn_checkAlarm_onclick = function(obj:nexacro.Button,e:nexacro.ClickEventInfo)
{
    var strParams = "ALARMCHECK";
    
    objEnv.userNotify(104, strParams);
    trace(strParams);
};

알람의 상태값을 설정할 문자열을 strParams 변수에 담아 userNotify 메소드를 호출하여 자바 코드로 전달합니다.

5

스마트폰 네비게이션 바의 뒤로가기(Back) 버튼의 터치 이벤트 함수를 작성합니다.

그림 17-11Screenshot_20190902-182514_wakemeup_2

뒤로가기 버튼을 포함한 네비게이션 바의 모양은 스마트폰 제조사에 따라 차이가 있을 수 있습니다.

사용자가 모바일 장치의 뒤로가기 버튼을 터치하면 앱을 종료할지 물어보고 "예"를 선택하면 앱을 종료시킵니다.

/* AlarmSet.xfdl */
this.AlarmSet_ondevicebuttonup = function(obj:nexacro.Form,e:nexacro.DeviceButtonEventInfo)
{
    //back button
    if(e.button == 2)
    {
        if(this.confirm("Do you want to quit?", "EXIT", "question"))
        {            
            var strParams = "ACTIVITYHIDE";                
            objEnv.userNotify(901, strParams);
            trace("EXIT");
        }
    }
};

위의 코드는 자바의 UserNotify 객체로 이벤트를 전달하여 종료 기능을 수행하도록 되어 있습니다. 따라서 『UI 디자인과 이벤트 처리』 단계에서 종료 기능을 동작시키려면 아래와 같이 코드를 수정해서 사용할 수 있습니다.


이 부분을

> var strParams = "ACTIVITYHIDE";

> objEnv.userNotify(901, strParams);


다음과 같이 수정

> var objApp = nexacro.getApplication();

> objApp.exit();

17.1.6xadl 함수 생성(자바 코드에서 호출할 함수)

1

애플리케이션(XADL) 파일을 편집 모드로 오픈합니다.

넥사크로 스튜디오의 [Project Explorer > App Information > Apps]에서 애플리케이션 파일을 더블 클릭하여 XADL 파일이 오픈되면 편집기에서 Script 탭을 선택합니다.

그림 17-12nexacro_studio_xadl_편집_01

2

XADL에 자바 코드로부터 전달되는 메시지를 처리하는 함수를 작성합니다.

사용자가 수행한 기능이 정상적으로 처리됐는지 자바 코드로부터 알림을 받으면 그 결과를 화면에 토스트 메시지로 출력하는 함수입니다. userNotify 메소드를 사용해 자바 코드쪽으로 토스트 메시지 호출을 요청합니다.

fn_callScript는 폼이 아닌 XADL에 선언해야 하는 메소드로 자바 코드 쪽의 callScript 메소드에 의해 호출됩니다.

만일 상태에 따라 어떤 동작을 할 필요가 있으면 switch 문의 각 case에 기능을 구현해줍니다.

/* App_Phone.xadl */
var objEnv = nexacro.getEnvironment();    //global variable

this.fn_callScript = function(strCallID, strMessage)
{
    switch(strCallID)
    {
        case "ALARMON":                
            objEnv.userNotify(1001, strMessage);
            break;
            
        case "ALARMOFF":
            objEnv.userNotify(1001, "Alarm has been stopped");                
            break;
            
        case "ALARMCANCEL":
            objEnv.userNotify(1001, "Alarm has been cancelled");    
            break;            
            
        case "ALARMCHECK":
            objEnv.userNotify(1001, "Alarm is found");    
            break;            
            
        case "NOALARM":
            objEnv.userNotify(1001, "No alarm is found");
            break;    
            
        default:
            trace("[fn_callScript] strCallID: "+ strCallID +" strMessage: "+ strMessage);
    }    
}

callScript 메소드의 사용법에 관한 설명은 자바에서 자바스크립트로 메시지 전달를 참조하십시오.

17.1.7UI 디자인 확인

현재까지 작업한 애플리케이션을 실행하여 화면에서 어떻게 보여지는지 확인합니다.

1

넥사크로 스튜디오의 메뉴에서 [Generate > Launch]를 실행합니다.

2

Run Configuration 창에서 Nexacro Emulator를 선택한 후 Run 버튼을 클릭합니다.

그림 17-13nexacrostudio_launch_01

3

작업한 결과가 화면에 이상없이 나오는지 확인합니다.

그림 17-14nexacrostudio_launch_02

이 앱은 모바일 용으로 디자인하려고 타이틀바와 상태바를 제거했습니다. 따라서 별도의 닫기(X) 버튼이 존재하지 않으므로 앱 실행시에는 Nexacro Browser보다는 Nexacro Emulator를 사용하는 것이 좋습니다. 만약 Nexacro Browser로 실행했을 경우에는 강제로 프로세스를 종료시켜줘야 합니다.

17.2배포 파일 생성

넥사크로 스튜디오에서 제작한 애플리케이션을 배포하기 위해 코드, 라이브러리, 리소스 파일들을 각각 하나의 파일로 묶는 패킹 작업을 수행합니다.

1

[Deploy > Packing(Archive&Update)] 메뉴를 실행하여 먼저 애플리케이션 산출물을 생성할 Output Path를 설정한 후 [Next >] 버튼을 클릭합니다.

2

패킹 위저드 좌측의 실행 환경에서 Android를 선택하고 다른 옵션은 사용자의 환경에 맞게 적절히 선택해준 후 하단의 [Packing] 버튼을 클릭합니다.

그림 17-15wakemeup_02

패킹 옵션에 관한 설명은 Packing (Archive&Update) 메뉴를 참조하십시오.

3

패킹 결과를 확인합니다.

패킹이 성공적으로 수행되면 다음과 같이 결과가 출력됩니다.

그림 17-16wakemeup_03_06

17.3UI만 포함된 앱 빌드

이 단계는 넥사크로 스튜디오에서 앱 빌더 서버로 연결해 새로운 앱 빌더 프로젝트를 생성하고, 넥사크로 애플리케이션을 적용할 수 있도록 설정된 안드로이드 프로젝트를 생성하여 앱 빌더 서버에 적용하는 과정입니다. 안드로이드 기능을 구현하는 경우 앱 빌더 서버에서 안드로이드 프로젝트를 내보내기 전에 반드시 한번은 빌드 앱 과정을 수행해야 합니다.

1

전 단계인 패킹이 완료된 후 다음과 같이 결과창이 나오면 하단의 [Build App] 버튼을 클릭합니다.

만일 패킹 위저드를 닫은 경우에는 [Deploy > Build App] 메뉴를 클릭하여 빌드 앱 위저드를 실행합니다.

그림 17-17wakemeup_03_06

2

Build Target 옵션에서 Android를 선택하고 AppBuilder Login Information에 앱 빌더 URL과 계정 정보를 입력한 후 [Next] 버튼을 클릭합니다.

그림 17-18wakemeup_03_08

3

Target Project 항목에서 [+] 버튼을 클릭하여 새로운 앱 빌더 프로젝트 이름과 앱에서 사용할 패키지 이름을 설정합니다.

빌드 앱 위저드의 설정 단계에서는 새로운 프로젝트 생성, 빌드 환경 설정을 비롯한 여러 옵션을 설정합니다. 여기서 설정된 패키지 이름은 앱 빌더 서버에서 프로젝트 이름으로 등록되고 앱의 패키지 이름으로 사용되므로 신중히 설정합니다. 덧붙여서 앱 빌더에서 내보내기 할 때의 안드로이드 프로젝트 이름도 이 값으로 설정됩니다.

그림 17-19wakemeup_03_07

Build Library는 앱에서 사용할 라이브러리를 설정할 수 있는 옵션으로 넥사크로 스튜디오와 같이 배포되는 버전을 사용해야 합니다. 사전에 앱 빌더 서버에서 등록한 라이브러리 목록이 출력되는데 정확한 정보를 모르면 "local library"를 선택합니다.

Signing은 안드로이드 앱 배포시 사용하는 디지털 서명 정보를 설정하는 옵션입니다. 사전에 앱 빌더 서버에 서명 정보와 키스토어 파일이 등록되어 있어야 목록이 출력됩니다.

4

새로운 프로젝트 이름을 설정하고 빌드 환경에서 옵션을 위와 같이 설정한 후 하단의 [Build] 버튼을 클릭하여 빌드를 수행합니다.

빌드가 성공적으로 수행되면 하단에 APK 파일 링크와 QR 코드가 표시됩니다.

그림 17-20wakemeup_03_05

네이버 앱을 비롯한 일부 QR 코드 스캐너에서는 내려받을 파일 이름을 잘못 인식하여 "wakemeup.apk; size=xxxx"와 같이 저장되는 오류가 있습니다. 이런 경우에는 파일을 APK로 인식하지 못하여 설치가 진행되지 않을 수 있으므로 파일 관리자에서 확장자를 APK로 변경 후 설치를 진행하던지 버그가 없는 다른 QR 코드 스캐너를 사용하시기 바랍니다.

빌드 앱 과정을 수행하면 앱 빌더 서버에 새로운 앱 빌더 프로젝트가 생성되며 더블어 넥사크로 앱을 구동할 수 있게 설정된 안드로이드 프로젝트도 자동으로 생성됩니다. 이 안드로이드 프로젝트에는 넥사크로 앱 구동에 필요한 넥사크로플랫폼 라이브러리, 설정 파일, 스플래시 이미지, 아이콘, 레이아웃 등의 리소스 등이 모두 포함되어 있어 별도의 수정없이 바로 사용이 가능합니다.

이 단계까지 완료하면 앱을 설치하여 실행시켜 볼 수 있습니다. 그러나 실제 알람 동작에 필요한 로직의 구현은 되어있지 않기 때문에 UI만 확인이 가능합니다.

17.4안드로이드 프로젝트 내보내기(Export)

앱에 안드로이드 기능을 추가하려면 앱 빌더 서버에서 넥사크로 앱 환경에 맞게 설정된 안드로이드 프로젝트를 내보내기 하여 사용자가 직접 추가 작업을 진행해야 합니다. 앱 빌더 서버는 내보내기 기능을 웹 인터페이스로 제공합니다.

1

웹 브라우저로 앱 빌더 서버로 접속하여 로그인합니다.

그림 17-21appbuilder_00

2

화면 상단에서 [App > App List] 메뉴를 클릭하여 앱 빌더 프로젝트 목록을 화면으로 이동합니다.

그림 17-22appbuilder_android_01

3

앱 빌더 프로젝트 목록에서 자신의 프로젝트를 더블 클릭하여 프로젝트 상세 화면으로 이동합니다.

그림 17-23appbuilder_android_01

4

프로젝트 상세 화면에서 [Android platform]을 클릭하여 안드로이드 설정 화면으로 이동합니다.

그림 17-24wakemeup_04_01

5

Project Modification 항목에서 [Export] 버튼을 클릭합니다. 내보내기 한 안드로이드 프로젝트는 압축된 형태의 [프로젝트 이름].zip 파일로 내려받을 수 있습니다.

그림 17-25wakemeup_04_01

17.5알람 기능 구현

앱 빌더 서버로부터 내보내기 하여 내려받은 기본 안드로이드 프로젝트를 베이스로 플랫폼에서 제공하는 라이브러리를 이용하여 알람 기능을 구현합니다.

먼저 내려받은 안드로이드 프로젝트 파일을 적당한 위치에 압축 해제한 후 안드로이드 스튜디오에서 프로젝트의 루트 디렉터리를 선택해 오픈합니다. 안드로이드 프로젝트의 경우에는 아래와 같이 디렉터리 아이콘이 안드로이드 스튜디오 아이콘으로 표시됩니다.

그림 17-26androidstudio_open_project

안드로이드 스튜디오에서 외부 프로젝트를 오픈 시 버전을 비롯한 여러 환경의 차이로 오류가 발생할 수 있습니다. 대부분의 경우에는 자동으로 동기화 작업이 수행되어 해결되지만 그렇지 못한 경우에는 수동으로 안드로이드 스튜디오 환경을 점검하여 해결하던지 아니면 최신 버전의 안드로이드 스튜디오를 사용하시길 바랍니다.


안드로이드 스튜디오 Project 윈도우의 보기 모드를 "Project"로 설정한 후 프로젝트의 디렉터리 구조를 보면 다음과 같습니다. 아래 그림에서 왼쪽은 넥사크로플랫폼에 맞게 앱 빌더에서 생성한 초기 디렉터리 구조입니다. 새로 생성하는 자바 클래스 파일은 java 디렉터리에 배치하고 각 리소스 파일은 리소스 성격에 맞는 디렉터리에 배치하여 구현 작업을 완료하면 오른쪽과 같이 됩니다.

그림 17-27android_studio_directory_structure_01

17.5.1알람음 리소스 추가

1

리소스 raw 디렉터리를 생성한 후 알람음으로 사용할 미디어 파일을 추가합니다.

먼저 안드로이드 스튜디오의 Project 윈도우에서 [src > main > res] 디렉터리 밑에 raw 디렉터리를 생성해야 합니다. 리소스 디렉터리 생성은 메뉴의 [File > New > Folder > Res Folder]를 실행하여 다음과 같이 생성해줍니다.

그림 17-28android_studio_new_res_folder_raw

그런 후 알람음으로 사용할 미디어 파일을 윈도우 탐색기로부터 복사하여 새로 생성한 raw 디렉터리로 붙여넣기 합니다.

그림 17-29android_studio_new_res_folder_raw_copy_file

raw 리소스 디렉터리는 원시 형태로 저장해두고 사용할 수 있는 리소스 파일을 배치하는 곳으로 코드상에서 리소스 ID(R.raw.filename)를 사용해 리소스 파일에 직접 접근할 수 있습니다.

17.5.2RingtonePlayingService 클래스 생성과 정의

1

RingtonePlayingService.java 파일을 생성합니다.

안드로이드 스튜디오의 메뉴에서 [File > New > Java Class]을 실행한 후 다음과 같이 설정하여 RingtonePlayingService.java 파일를 생성합니다.

그림 17-30android_studio_new_java_class_RingtonePlayingService

2

RingtonePlayingService 클래스를 정의합니다.

앞 단계에서 생성한 RingtonePlayingService.java 파일을 오픈하여 다음과 같이 RingtonePlayingService 클래스를 정의합니다. RingtonePlayingService 클래스는 알람음을 재생하기 위해 사용합니다. 앱의 상태와 관계없이 동작해야 하므로 서비스 형태로 동작합니다.

/* RingtonePlayingService.java */
package com.demo.wakemeup;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import android.support.annotation.Nullable;

import com.nexacro.NexacroActivity;

public class RingtonePlayingService extends Service {

    MediaPlayer mediaPlayer;
    int startId;
    boolean isRunning;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        String getState = intent.getExtras().getString("state");
        assert getState != null;

        switch (getState) {
            case "ALARMON":
                startId = 1;
                break;
            case "ALARMOFF":
            default:
                startId = 0;
                break;
        }

        // 알람음 재생중이 아니면 알람음 재생 시작
        if(!this.isRunning && startId == 1) {
            mediaPlayer = MediaPlayer.create(this, R.raw.anything);
            mediaPlayer.start();

            this.isRunning = true;
            this.startId = 0;
        }

        // 알람음 재생중이면 알람음 재생 종료
        else if(this.isRunning && startId == 0) {
            mediaPlayer.stop();
            mediaPlayer.reset();
            mediaPlayer.release();

            this.isRunning = false;
            this.startId = 0;
        }

        // 알람음 재생중이 아니면 알람음 재생종료
        else if(!this.isRunning && startId == 0) {
            this.isRunning = false;
            this.startId = 0;
        }

        // 알람음 재생중이면 알람음 재생 시작
        else if(this.isRunning && startId == 1){
            this.isRunning = true;
            this.startId = 1;
        }

        Intent nIntent = new Intent(this, NexacroActivity.class);
        nIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        startActivity(nIntent);

        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }
}

AlarmReceiver에 의해 서비스가 시작되면 알람 상태값을 인텐트로부터 받아 알람 상태에 따라 MediaPlayer를 사용해 알람음을 재생하거나 멈추는 동작을 수행합니다. 그리고 사용자가 알람음 재생을 멈출 수 있도록 NexacroActivity를 시작하여 앱을 실행해 줍니다.

MediaPlayer 클래스 사용에 관한 설명은 미디어 플레이어를 사용한 알람음 재생을 참조하십시오.

앱 리소스에 등록되어 있는 알람 음원을 사용하는 방법은 앱 리소스 사용을 참조하십시오.

17.5.3AlarmReceiver 클래스 생성과 정의

1

AlarmReceiver.java 파일을 생성합니다.

안드로이드 스튜디오의 메뉴에서 [File > New > Java Class]을 실행한 후 다음과 같이 설정하여 AlarmReceiver.java 파일를 생성합니다.

그림 17-31android_studio_new_java_class_AlarmReceiver

2

AlarmReceiver 클래스를 정의합니다.

앞 단계에서 생성한 AlarmReceiver.java 파일을 오픈하여 다음과 같이 AlarmReceiver 클래스를 정의합니다. AlarmReceiver는 알람 발생시 시스템으로부터 이벤트를 수신하는 브로드캐스트 리시버 클래스입니다. 알람 이벤트가 발생하면 onReceive 메소드가 수행되며 알람음을 재생하는 서비스를 시작합니다.

/* AlarmReceiver.java */
package com.demo.wakemeup;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class AlarmReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // intent로부터 전달받은 알람 상태값 ALARMON
        String strAlarmState = intent.getExtras().getString("state");

        // RingtonePlayingService 서비스 intent 생성
        Intent service_intent = new Intent(context, RingtonePlayingService.class);

        // RingtonePlayinService intent에 알람 상태값 저장
        service_intent.putExtra("state", strAlarmState);

        // 안드로이드 SDK 버전에 맞게 알람음 서비스 시작
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
            context.startForegroundService(service_intent);
        }
        else {
            context.startService(service_intent);
        }
    }
}

서비스를 시작하려면 인텐트를 생성해줘야 합니다. 서비스 호출에 사용할 인텐트는 호출할 RingtonePlayingService 클래스를 명시적으로 지정해서 생성해 줍니다. 그리고 생성된 인텐트에는 putExtra 메소드를 사용해 현재 알람의 상태값을 저장해 줍니다.

마지막으로 안드로이드 버전에 맞게 startService 혹은 startForegroundService 메소드를 호출해 줍니다.

서비스에 관한 좀 더 자세한 설명은 Service 수행과 종료를 참조하십시오.

17.5.4UserNotify 클래스 생성과 정의

1

UserNotify.java 파일을 생성합니다.

안드로이드 스튜디오의 메뉴에서 [File > New > Java Class]을 실행한 후 다음과 같이 설정하여 UserNotify.java 파일를 생성합니다.

그림 17-32android_studio_new_java_class_UserNotify

2

UserNotify 클래스를 정의합니다.

앞 단계에서 생성한 UserNotify.java 파일을 오픈하여 다음과 같이 UserNotify 클래스를 정의합니다. UserNotify 클래스는 넥사크로 화면에서 발생한 자바스크립트 이벤트를 자바 코드쪽으로 전달해주는 기능을 합니다. 넥사크로의 자바스크립트 코드에서 usernotify 메소드를 호출하면 UserNotify 객체의 onUserNotify 이벤트 함수가 동작하게 됩니다.

/* UserNotify.java */
package com.demo.wakemeup;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import com.nexacro.NexacroActivity;
import com.nexacro.event.NexacroEventHandler;

import java.util.Calendar;
import java.util.Locale;

import static android.content.Context.ALARM_SERVICE;

public class UserNotify implements NexacroEventHandler {

    AlarmManager alarmManager;
    Context context;
    PendingIntent pendingIntent;

    Intent alarmIntent;     //알람 리시버용 인텐트

    NexacroActivity nActivity = com.nexacro.NexacroActivity.getInstance();

    public UserNotify(Context mContext) {
        this.context = mContext;

        this.alarmIntent = new Intent(this.context, AlarmReceiver.class);

        if(NexacroActivity.getInstance() != null)
            NexacroActivity.getInstance().setNexacroEventListener(this);
    }

    @Override
    public void onUserNotify(int nNotifyID, String strMessage) {
        String strMsg = "";

        // 알람매니저 설정
        alarmManager = (AlarmManager) this.context.getSystemService(ALARM_SERVICE);

        switch(nNotifyID)
        {
            case 101:
                /* Alarm set code */
            case 102:
                /* Alarm off code */
            case 103:
                /* Alarm cancel code */
            case 104:
                /* Alarm check code */
            case 901:
                /* Activity hidden code */
            case 1001:
                /* Toast messsage */
            default:
                Log.i("UserNotify", "Unknown nNotifyID: "+ nNotifyID);
        }//end of switch(nNotifyID)
    }

    public void showToast(String strMessage) {
        Toast.makeText(this.context, strMessage, Toast.LENGTH_LONG).show();
    }
}

onUserNotify 이벤트 함수는 메시지의 ID값을 갖는 nNotifyID와 문자열 메시지를 갖는 strMessage 값을 매개변수로 받을 수 있습니다. onUserNotify 이벤트 함수는 전달받은 nNotifyID 값에 따라 앱이 어떤 동작을 수행할지 분기 처리합니다. 현재 넥사크로 자바스크립트에서 넘겨주는 nNotifyID와 strMessage 값은 다음과 같습니다.

nNotifyID

strMessage

설명

참조 설명

101

"2019,5,29,18,17"

알람 설정, 사용자가 설정한 알람 시간 설정값

Calendar 오브젝트 시간 설정


AlarmManager를 사용한 알람 설정, 해제, 취소, 확인

102

"ALARMOFF"

알람 중지(알람음 재생 중지)

AlarmManager를 사용한 알람 설정, 해제, 취소, 확인

103

"ALARMCANCEL"

알람 취소(설정한 알람 취소)

AlarmManager를 사용한 알람 설정, 해제, 취소, 확인

104

"ALARMCHECK"

알람 확인(설정한 알람이 있는지 확인)

AlarmManager를 사용한 알람 설정, 해제, 취소, 확인

901

"ACTIVITYHIDE"

앱 종료 혹은 모바일 장치의 홈 화면으로 이동

앱의 종료

앱을 백그라운드로 전환

1001

[기능 수행 결과 메시지]

UI로부터 전달된 문자열을 화면에 토스트 메시지로 출력


case 101: 알람 설정

/* UserNotify.java */
switch(nNotifyID)
{
    case 101:   //Alarm Setting
        String[] strRecvTime = strMessage.split(",", 5);

        // Calendar 객체 생성
        final Calendar calendar = Calendar.getInstance();

        // calendar에 시간 셋팅
        calendar.set(Calendar.YEAR, Integer.parseInt(strRecvTime[0]));
        calendar.set(Calendar.MONTH, Integer.parseInt(strRecvTime[1])-1);
        calendar.set(Calendar.DATE, Integer.parseInt(strRecvTime[2]);
        calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(strRecvTime[3]));
        calendar.set(Calendar.MINUTE, Integer.parseInt(strRecvTime[4]));
        calendar.set(Calendar.SECOND, 0);

        // receiver에 알람 상태값 설정
        alarmIntent.putExtra("state","ALARMON");

        // BroadcastReceiver를 시작하는 인텐트를 생성
        pendingIntent = PendingIntent.getBroadcast(this.context,
                0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        // 알람 반복 셋팅
        long aTime = System.currentTimeMillis();
        long bTime = calendar.getTimeInMillis();

        long oneday = 1000 * 60 * 60  * 24;

        while(aTime>bTime){
            bTime += oneday;
        }

        //알람 설정. RTC_WAKEUP: 지정된 시간에 펜딩인텐트를 작동시키기 위해 장치를 깨움.
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, bTime, oneday, pendingIntent);

        strMsg = "This alarm will ring at " +
                calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.ENGLISH) + " " +
                calendar.get(Calendar.DATE) + ", " +
                calendar.get(Calendar.YEAR) + " " +
                calendar.get(Calendar.HOUR_OF_DAY) + ":" +
                calendar.get(Calendar.MINUTE);

        if(nActivity != null) {
            nActivity.callScript("fn_callScript('ALARMON', '"+strMsg+"')");
        }

        break;

case 102: 알람 중지(알람음 재생 중지)

/* UserNotify.java */
switch(nNotifyID)
{
    ..
    case 102:   //Alarm OFF
        //알람매니저 취소
        alarmManager.cancel(pendingIntent);
        alarmIntent.putExtra("state","ALARMOFF");

        //서비스로 ALARMOFF 전달하여 알람음 재생 중지
        this.context.sendBroadcast(alarmIntent);

        if(nActivity != null) {
            nActivity.callScript("fn_callScript('ALARMOFF', '"+strMsg+"')");
        }

        break;

case 103: 알람 취소(설정한 알람 취소)

/* UserNotify.java */
switch(nNotifyID)
{
    ..
    case 103:   //Alarm CANCEL
        Intent cancelIntent = new Intent(this.context, AlarmReceiver.class);
        PendingIntent cancelSender = PendingIntent.getBroadcast(this.context, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT);

        if(cancelSender != null) {
            alarmManager.cancel(cancelSender);
            cancelSender.cancel();

            if(nActivity != null) {
                nActivity.callScript("fn_callScript('ALARMCANCEL', '"+strMsg+"')");
            }
        }
        else {
            if(nActivity != null) {
                nActivity.callScript("fn_callScript('NOALARM', '"+strMsg+"')");
            }
        }

        break;

case 104: 알람 확인(설정한 알람이 있는지 확인)

/* UserNotify.java */
switch(nNotifyID)
{
    ..
    case 104:   //Alarm CHECK
        Intent checkIntent = new Intent(this.context, AlarmReceiver.class);
        PendingIntent checkSender = PendingIntent.getBroadcast(this.context, 0, checkIntent, PendingIntent.FLAG_NO_CREATE);

        if(checkSender == null) {
            if(nActivity != null) {
                nActivity.callScript("fn_callScript('NOALARM', '"+strMsg+"')");
            }
        }
        else {
            if(nActivity != null) {
                nActivity.callScript("fn_callScript('ALARMCHECK', '"+strMsg+"')");
            }
        }

        break;

case 901: 앱 종료 혹은 모바일 장치의 홈 화면으로 이동

/* UserNotify.java */
switch(nNotifyID)
{
    ..
    case 901:   //ACTIVITY hidden 처리
        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        this.context.startActivity(intent);
        break;

case 1001: 토스트 메시지 출력

/* UserNotify.java */
switch(nNotifyID)
{
    ..
    case 1001:  //TOAST message
        showToast(strMessage);
        break;

위 Switch 문의 각 분기에서 현재 동작상황을 넥사크로 UI로 알려주기위해 callScript 메소드를 사용합니다. callScript 메소드는 자바에서 넥사크로 자바스크립트의 메소드를 호출하는 용도로 사용되는데 호출하면서 문자열 변수를 같이 전달할 수 있기 때문에 메시지를 전달하는 용도로도 사용이 가능합니다.

usernotify 메소드에 관한 자세한 설명은 자바스크립트에서 자바로 메시지 전달에서 usernotify 메소드 편을 참조하십시오.

callScript 메소드에 관한 자세한 설명은 자바에서 자바스크립트로 메시지 전달를 참조하십시오.

17.5.5부트스트랩 설정과 UserNotify 객체 생성

MainActivity.java 파일에서 부르스트랩, 프로젝트 URL을 설정하고 UserNotify 객체를 생성해줍니다.

앱 빌더 서버가 아닌 별도의 배포 서버를 운용하는 경우에는 Bootstrap URL과 Project URL을 배포 서버에 맞게 설정해 줍니다. MainActivity.java 파일을 오픈하여 MainActivity 생성자에서 설정 정보를 수정합니다. 앱 빌더 서버를 배포 서버로 사용한다면 수정하지 않고 다음으로 넘어갑니다.

/* MainActivity.java */
public class MainActivity extends NexacroUpdatorActivity {

    public MainActivity() {
        super();
        setBootstrapURL("http://[Bootstrap URL]/appbuilder/archives/629/_android_/start_android.json");
        setProjectURL("http://[Project URL]/appbuilder/archives/629/_android_/");        
    }
        .
        .

MainActivity 클래스에 onDestroy 메소드가 정의되어 있는지 확인하고 없으면 생성해 줍니다. 그리고 다음과 같이 UserNotify 객체를 생성해 주는 코드를 삽입합니다. UserNotify 객체는 넥사크로 UI의 자바스크립트 코드에서 usernotify 메소드 호출시 발생하는 이벤트를 처리해주는 클래스입니다.

/* MainActivity.java */
protected void onDestroy() {
    new UserNotify(this);
    super.onDestroy();
}

MainActivity 클래스 설정에 관한 자세한 설명은 MainActivity 설정을 참조하십시오.

17.5.6브로드캐스트 리시버와 서비스 추가

앱 매니페스트(AndroidManifest.xml)에 앱의 구성요소인 브로드캐스트 리시버와 서비스를 추가합니다.

AndroidManifest.xml 파일을 오픈하여 알람 발생시 이벤트를 수신할 AlarmReceiver와 알람음 재생에 사용할 RingtonePlayingService 서비스를 application의 하위 요소로 다음과 같이 추가해 줍니다.

<!-- AndroidManifest.xml -->
<application>
    ...
    <receiver android:name=".AlarmReceiver" />
    <service
        android:name=".RingtonePlayingService"
        android:enabled="true">
    </service>
</application>

17.6안드로이드 프로젝트 들여오기(Import)

앱 빌더 서버의 웹 콘솔을 통해서 구현 작업이 완료된 안드로이드 프로젝트를 앱 빌더 서버로 들여오기 합니다. 수정된 안드로이드 프로젝트를 앱 빌더 서버로 들여오기 하면 향후 앱을 빌드할 때 새로 수정된 안드로이드 프로젝트를 기반으로 빌드가 수행됩니다.

1

수정된 안드로이드 프로젝트의 루트 디렉터리를 통째로 zip 형식으로 압축하여 "[프로젝트 이름].zip" 파일을 생성합니다.

2

앱 빌더 서버의 프로젝트 상세 화면으로 들어가서 Project Modification 항목에서 [Import] 버튼을 클릭합니다.

파일을 선택 팝업에서 파일을 선택하면 업로딩이 수행되는데 웹 브라우저의 상태표시줄에서 업로딩 상태를 확인할 수 있습니다. 들여오기 기능은 내보내기 기능과 같은 위치에 있습니다.

그림 17-33wakemeup_04_01

안드로이드 프로젝트를 압축하여 들여오기 할 때 앱 빌더의 프로젝트 이름과 동일하게 설정해야 합니다. 이름이 다르면 들여오기가 실패하며 수정 내용이 서버에 반영되지 않으므로 주의하십시오.

3

들여오기 옵션을 선택합니다.

프로젝트 파일의 업로딩이 완료되면 다음과 같이 들여오기 옵션을 선택할 수 있는 팝업이 오픈됩니다. 앱 빌더 콘솔에서 프로젝트 설정을 수정할 수 없게 하려면 [YES], 있게 하려면 [NO]를 선택합니다. 여기서는 [YES]를 선택합니다.

그림 17-34wakemeup_06_02

17.7전체 앱 빌드

이 과정은 기본적으로 앞에서 설명한 UI만 포함된 앱 빌드 과정과 동일합니다. 다만 앞의 과정에서 앱 빌더 프로젝트와 안드로이드 프로젝트의 생성과 설정을 완료했으므로 앞의 빌드 앱 과정에서 생성한 앱 빌더 프로젝트만 Target Project 옵션에 지정해줍니다.

1

넥사크로 스튜디오의 [Deploy > Build App] 메뉴를 클릭하여 빌드 앱 위저드를 실행합니다.

2

[빌드 앱 위저드 > Build Target] Build Target 항목에서 Android를 선택하고 AppBuilder Login Information에 앱 빌더 URL과 계정 정보를 입력한 후 [Next] 버튼을 클릭합니다.

3

[빌드 앱 위저드 > AppBuilder] Target Project 항목에서 앞의 빌드 앱 과정에서 생성했던 프로젝트를 선택하고 적절한 옵션을 선택 후 하단의 [Build] 버튼을 클릭하여 빌드를 진행합니다.

4

빌드 결과를 확인합니다.

빌드가 성공적으로 수행되면 하단에 APK 파일 링크와 QR 코드가 표시됩니다.

그림 17-35wakemeup_03_05

넥사크로 스튜디오가 설치되어 있지 않거나 사용할 수 없는 환경에서는 앱 빌더의 웹 인터페이스를 통해 직접 빌드 앱 작업을 진행할 수 있습니다. 더 자세한 내용은 앱 빌더에서 직접 빌드하기를 참조하십시오.

17.8앱 설치 및 실행

알람 앱 제작을 완료했으면 스마트폰으로 앱을 내려받아 설치한 후 실행해 봅니다. 시간을 설정한 후 알람을 설정/해지시켜 봅니다. 버튼 터치하면 알람 기능이 수행되며 하단에 토스트 메시지가 출력됩니다.

그림 17-36alarmapp_demo_02_480

17.8.1앱 설치

앞 단계의 앱 빌드 결과물로 생성된 QR 코드를 스마트폰의 QR 코드 리더기로 읽거나, URL을 직접 웹 브라우저에서 오픈하여 앱 패키지(APK) 파일을 내려받습니다. 다운로딩이 완료되면 내려받은 앱 패키지(APK) 파일을 실행하여 설치합니다.

17.8.2알람 설정/중지

알람을 설정한 후 알람 음이 발생하면 알람을 중지 시켜 봅니다. 버튼을 터치하여 기능이 수행되면 앱 화면 하단에 토스트 메시지가 출력되어 결과를 확인할 수 있습니다.

1

날짜와 시간을 설정한 후 Alarm Setting 버튼을 터치합니다.

그림 17-37wakemeup_toast_알람설정

2

Alarm CHECK 버튼을 터치하여 알람이 설정됐는지 확인합니다.

그림 17-38wakemeup_toast_알람체크_on

3

설정된 시간에 알람음이 재생되는지 확인하고, Alarm OFF 버튼을 터치하여 알람음 재생이 멈추는지 확인합니다.

그림 17-39wakemeup_toast_알람중지

17.8.3알람 설정/취소

알람을 설정했다가 알람을 취소 시켜 봅니다. 버튼을 터치하여 기능이 수행되면 앱 화면 하단에 토스트 메시지가 출력되어 결과를 확인할 수 있습니다.

1

날짜와 시간을 설정한 후 Alarm Setting 버튼을 터치합니다.

그림 17-40wakemeup_toast_알람설정

2

Alarm CHECK 버튼을 터치하여 알람이 설정됐는지 확인합니다.

그림 17-41wakemeup_toast_알람체크_on

3

Alarm CANCEL 버튼을 터치하여 설정한 알람을 취소합니다.

그림 17-42wakemeup_toast_알람취소

4

Alarm CHECK 버튼을 터치하여 알람이 취소됐는지(설정된 알람이 없는지) 확인합니다.

그림 17-43wakemeup_toast_알람체크_off

17.9앱 데모 및 소스코드

17.9.1알람 앱 내려받기

모바일 장치에서 알람 앱 데모를 내려받아 설치하려면 QR 코드 스캐너로 아래 이미지를 스캔하십시오.

그림 17-44wakemeup_qrcode_android

17.9.2알람 앱 소스 코드 내려받기

알람 앱 데모의 소스 코드를 내려받으려면 다음 표의 링크를 클릭하십시오.

넥사크로플랫폼 프로젝트

https://github.com/TOBESOFT-DOCS/demo_nexacroplatform_17_WakeMeUp/archive/master.zip

안드로이드 프로젝트

https://github.com/TOBESOFT-DOCS/demo_android_wakemeup/archive/master.zip

안드로이드 프로젝트 소스는 넥사크로 플랫폼 환경에 맞게 설정되어 있으며 앱 빌더에 들여오기(Import)해서 사용할 수 있습니다.

17.10참고

17.10.1앱 빌더에서 직접 빌드하기

앱 빌더는 넥사크로 스튜디오를 통해서도 사용이 가능하지만 앱 빌더에서 제공하는 웹 인터페이스를 통해서도 사용이 가능합니다. 이는 개발자뿐 아니라 앱 개발 지식이 없거나 개발 환경을 구성하기 힘든 관리자 등이 앱을 빌드할 수 있도록 도와줍니다.

이번 장에서는 사용자가 넥사크로 스튜디오를 사용하지 않고 직접 웹 인터페이스를 사용해 앱을 빌드하는 과정을 설명합니다.

이 과정은 넥사크로 스튜디오를 사용해 빌드 앱 과정을 한번 수행한 이후에 진행할 수 있습니다. 그 이유는 빌드 앱을 수행하는 과정에서 앱 빌더가 앱을 빌드 할 수 있는 환경을 구성하기 때문입니다.

1

웹 브라우저로 앱 빌더 서버로 접속하여 로그인합니다.

그림 17-45appbuilder_00

2

화면 상단에서 [App > App List] 메뉴를 클릭하여 앱 빌더 프로젝트 목록을 화면으로 이동합니다.

그림 17-46appbuilder_android_01

3

앱 빌더 프로젝트 목록에서 자신의 프로젝트를 더블 클릭하여 프로젝트 상세 화면으로 이동합니다.

그림 17-47appbuilder_android_01

4

[App Info] 메뉴에서 빌드 준비가 되어 있는지 확인합니다.

앞 단계의 프로젝트 목록에서 프로젝트 항목을 더블 클릭하면 [App Info] 화면으로 이동합니다. 여기서 빌드 준비가 되어 있는지 Target OS와 Nexacro application resource 항목을 확인합니다.

Target OS는 빌드를 수행할 운영체제를 선택하는 항목으로 빌드를 원하는 운영체제에 체크가 되어 있는지, 부트스트랩 파일이 등록되어 있는지 확인합니다.

그림 17-48appbuilder_build_application_target_OS

Nexacro application resource는 빌드를 수행할 애플리케이션 파일을 확인하는 항목입니다. 빌드에 필요한 넥사크로 애플리케이션 코드와 라이브러리, 리소스 파일이 등록되어 있는지 확인합니다. 여기서 보이는 apk 파일은 빌드를 한번이라도 수행시 자동으로 등록되는 항목입니다.

그림 17-49appbuilder_build_application_nexacro_application_resource

Target OS가 체크되어 있지 않거나 Nexacro application resource 파일이 등록되어 있지 않으면 빌드가 수행되지 않습니다. UI만 포함된 앱 빌드 과정을 먼저 진행하십시오.

5

[Build Application] 메뉴를 클릭하여 빌드 화면으로 이동한 후 [Start to build] 버튼을 클릭하여 빌드를 수행합니다.

그림 17-50appbuilder_build_application

6

빌드 결과를 확인합니다.

그림 17-51appbuilder_build_application_2

메뉴

설명

1 운영체제

운영체제 이름을 표시해줍니다. 앱 빌더는 안드로이드, iOS, 맥OS를 지원합니다.

2 패키지 파일 다운로드 링크

빌드된 패키지 파일(APK)을 다운로드 할 수 있는 링크입니다.

3 QR 코드

QR 코드 스캐너를 사용해 패키지 파일을 내려받을 수 있는 링크입니다.

4 Rebuild

빌드 작업을 다시 수행합니다.

5 View Log

빌드 로그를 보여줍니다.

6 Last Build Time

최종 빌드 시간을 보여줍니다.

7 빌드 성공 여부

빌드가 성공하면 O 표시, 실패하면 X 표시를 합니다.

8 Rebuild All

모든 운영체제의 빌드 작업을 다시 수행합니다.

만약 빌드에 실패하면 다음과 같이 에러 메시지 창이 표시됩니다.

그림 17-52appbuilder_build_application_3

17.10.2자바스크립트와 자바간 통신 방법

자바스크립트에서 자바로 메시지 전달

넥사크로플랫폼 자바스크립트에서 안드로이드 자바로 메시지를 전달할 수 있는 방법은 execExtAPI 메소드를 사용하는 방법과 userNotify 메소드를 사용하는 방법 두 가지가 있습니다. 두 방식 모두 메시지를 전달할 수 있지만 구현 방법 및 특성이 다르므로 용도에 맞게 선택합니다.


execExtAPI 메소드

userNotify 메소드

동작 환경

안드로이드, iOS

안드로이드

용도

앱 간의 메시지 전달. 자신을 포함한 안드로이드 시스템 상의 모든 앱으로 전달 가능.

앱 내부 객체간 메시지 전달

오브젝트

ExternalAPI

Environment

전달 인수

Receive ID

Application ID

API name

Parameter

Notify ID

Message

동작 방식

브로드캐스팅 방식. 안드로이드 브로드캐스트 스킴을 사용하여 메시지를 전달. 자바 브로드캐스트 리시버를 통해 메시지를 받을 수 있음.

이벤트 방식. 자바스크립트에서 전달한 메시지를 자바 이벤트 함수를 통해 받을 수 있음.

execExtAPI 메소드

ExternalAPI는 앱 간의 연동이 필요한 경우에 사용할 수 있는 오브젝트입니다. execExtAPI는 메시지 전달을 수행하는 메소드로 메시지를 전달하여 외부 앱을 실행하거나 특정 기능을 수행하도록 할 수 있습니다. 물론 모든 앱과의 연동이 가능한 것은 아니며 자바스크립트에서 보내는 메시지를 받고 처리하는 로직이 자바로 구현된 앱과만 연동할 수 있습니다. 다음은 자바스크립트에서 execExtAPI 메소드를 사용해 메시지를 전달하는 예입니다.

/* nexacro */
var strRecvID = "nexacrowakemeup";

var strApplicationID = "com.nexacro.wakemeup";
var strAPI = "setAlarm";
var strParams = sCurrentTime;
    
this.ExternalAPI00.execExtAPI(strRecvID, strApplicationID, strAPI, strParams);

안드로이드 환경에서는 execExtAPI 메소드를 호출하면 메시지가 브로드캐스트 형식으로 전달됩니다. 따라서 이 메시지를 받으려면 자바에서 해당 메시지를 받을 수 있도록 인텐트 필터와 브로드캐스트 리시버가 설정되어 있어야 합니다. 인텐트 필터는 안드로이드 시스템에서 보내는 수많은 브로드캐스트 메시지 중 원하는 메시지를 골라서 받을 때 사용합니다. 브로드캐스트 리시버는 전달받은 브로드캐스트 메시지의 처리를 담당합니다. 다음은 브로드캐스트 메시지를 자바에서 받을 수 있도록 AndroidManifest.xml에 브로드캐스트 리시버와 인텐트 필터를 설정한 예입니다.

<!-- AndroidManifest.xml -->
<receiver android:name=".ExternalAPIReceiver" android:enabled="true">
    <intent-filter>
        <action android:name="com.nexacro.wakemeup.extAPI" />
    </intent-filter>
</receiver>

안드로이드 시스템으로부터 전달받은 브로드캐스트 메시지는 브로드캐스트 리시버로 등록한 ExternalAPIReceiver 객체의 onReceive 메소드에서 인텐트 객체를 통해 추출할 수 있습니다. 다음은 브로드캐스트 리시버 클래스의 onReceiver 메소드에서 인텐트 객체로부터 메시지를 추출하는 예를 보여줍니다.

/* Android */
public class ExternalAPIReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //브로드캐스트 메시지를 받으면 인텐트로부터 받은 내용을 로그로 출력
        Log.i("nexacro", ">> [com.nexacro.wakemeup] message received.");
        Log.i("nexacro", "- sender:" + intent.getExtras().getString("sender"));
        Log.i("nexacro", "- recvID:" + intent.getExtras().getString("recvID"));
        Log.i("nexacro", "- apiname:" + intent.getExtras().getString("apiname"));
        Log.i("nexacro", "- params:" + intent.getExtras().getString("params"));
    }
}

인텐트 객체로부터 메시지를 추출할 때는 getExtras 메소드를 사용하고 인텐트 객체에 데이터를 저장할 땐 putExtra 메소드를 사용합니다. getExtras, putExtra 메소드 사용시 데이터 타입에 따라 getString, getInt 등의 메소드를 이용 할 수 있습니다.

String name = intent.getExtras().getString("name");
int age = intent.getExtras().getInt("age");

userNotify 메소드

userNotify는 Environment 오브젝트의 메소드로 자바스크립트에서 자바로 메시지를 전달 할 때 사용합니다. 메시지는 문자열 형태로 전달되며 메시지마다 ID를 설정할 수 있어 간단한 메시지 관리가 가능합니다.

nexacro.Environment.userNotify(nNotifyID, strMessage)

다음은 자바스크립트에서 userNotify 메소드를 사용하는 예입니다.

/* nexacro */
var objEnv = nexacro.getEnvironment();
objEnv.userNotify( 200, "Hello usernotify!" );

userNotify 메소드를 사용해 자바스크립트 코드에서 전달한 메시지는 자바 코드에서 이벤트 형태로 받을 수 있는데 이 때, 사용되는 이벤트 함수가 onUserNotify 입니다. 자바에서 onUserNotify 이벤트 함수를 사용하려면 우선 NexacroEventHandler 인터페이스를 상속받아 다음과 같이 UserNotify 클래스를 정의합니다. 그리고 onUserNotify 이벤트 함수를 재정의 하여 필요한 처리를 해줍니다.

/*
    UserNotify.java: UserNotify 클래스
*/

package com.nexacro.wakemeup;

import android.util.Log;

import com.nexacro.NexacroActivity;
import com.nexacro.event.NexacroEventHandler;

public class UserNotify implements NexacroEventHandler {

    public UserNotify() {
      if(NexacroActivity.getInstance() != null)
          NexacroActivity.getInstance().setNexacroEventListener(this);
    }

    @Override
    public void onUserNotify(int nNotifyID, String strMessage) {
        // TODO :
        //Log.d("UserNotify", "onUserNotify nNotifyID : " + nNotifyID + " strMessage : " + strMessage);        
    }
}

클래스를 정의했으면 다음은 이를 사용할 수 있도록 UserNotify 객체를 생성합니다. UserNotify 객체 생성은 그 시점이 중요한데 넥사크로 액티비티 로딩이 완료된 이후에 생성해야 합니다. 넥사크로 엔진 업데이트 작업이 완료되고 넥사크로 액티비티가 화면에 로딩된 이후, 예를 들면 MainActivity가 종료되는 시점인 onDestroy에서 해주거나 그 이후 프로세스에 해당하는 Activity나 서비스 등에서 생성해줍니다. 다음은 MainActivity가 종료되는 시점에 UserNotify 객체의 인스턴스를 생성해주는 예를 보여줍니다.

/* Android */
public class MainActivity extends NexacroUpdatorActivity {
        .
        .
    @Override
    protected void onDestroy() {
        new UserNotify();
        super.onDestroy();
    }
}

위와 같이 UserNotify 객체를 생성한 다음에는 자바스크립트 코드에서 userNotify 메소드를 호출할 때마다 자바 코드에서 onUserNotify 이벤트 함수가 호출됩니다.

userNotify 메소드는 2019년 4월 정기 버전(17.0.0.1900)부터 지원하며 안드로이드 넥사크로 브라우저 환경에서만 사용이 가능합니다.

자바에서 자바스크립트로 메시지 전달

callScript 메소드

자바 코드에서 자바스크립트로 메시지를 보낼 때는 callScript 메소드를 사용합니다. callScript는 자바 코드쪽에서 XADL에 선언되어 있는 자바스크립트 메소드를 호출하는 기능인데 인수로 문자열 형식의 메시지를 전달할 수 있습니다. callScript는 NexacroActivity 클래스에 정의되어 있으며 사용 방법은 다음과 같습니다.

/* JAVA */
NexacroActivity act = NexacroActivity.getInstance();
if(act!=null)act.callScript("fn_test('Hello nexacro!')");

자바 코드에서 위와 같이 메소드를 호출하면 자바스크립트 코드의 fn_test 메소드가 호출됩니다. 단, callScript() 호출시 인수로 설정한 "fn_test('arguments')"와 동일한 함수 명으로 넥사크로의 XADL에 메소드가 정의되어 있어야 합니다.

/* Javascript in XADL file */
this.fn_test = function(arg)
{
    trace("[testAlarm]: arg="+ arg);
};

17.10.3앱 시작점 지정

안드로이드 앱에서는 별도의 main 함수가 존재하지 않기 때문에 사용자가 직접 앱의 시작점을 설정해야 합니다. 일반적으로는 프로젝트 생성시 디폴트로 MainActivity가 시작점으로 지정되지만 필요에 따라 다른 activity로도 변경이 가능합니다. 앱의 시작점은 AndroidManifest.xml 파일에서 다음과 같이 설정할 수 있습니다.

<!-- AndroidManifest.xml -->
<activity
    android:name="com.demo.wakemeup.MainActivity"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

우선 앱 시작시 실행될 activity를 선언하고 해당 activity에 intent-filter를 설정합니다. intent-filter의 action은 "android.intent.action.MAIN", category는 "android.intent.category.LAUNCHER"로 설정하면 앱 실행시 가장 먼저 해당 intent-filter가 설정된 activity가 실행됩니다. 여기서는 activity의 이름을 MainActivity로 설정합니다.

17.10.4MainActivity 설정

MainActivity에서는 앱 실행에 필요한 Bootstrap URL(start_android.json), Project URL(제너레이트된 프로젝트 소스)을 설정하고 NexacroUpdatorActivity의 인스턴스를 생성합니다. NexacroUpdatorActivity에서는 설정된 URL의 정보를 확인하여 업데이트 작업을 수행한 후 넥사크로 애플리케이션 화면을 출력하는 NexacroActivity를 호출합니다.

NexacroUpdatorActivity 클래스나 NexacroActivity 클래스는 앱 빌드시 등록한 라이브러리(nexacro17.android.jar)에서 제공하므로 사용자가 별도로 구현할 필요는 없습니다. 사용자는 MainActivity 클래스 선언시 NexacroUpdatorActivity를 상속받아 다음과 같이 구현해줍니다.

/* MainActivity.java*/
public class MainActivity extends NexacroUpdatorActivity {

    public MainActivity() {
        super();

        setBootstrapURL("http://[Bootstrap File URL]/start_android.json");
        setProjectURL("http://[Generated Project URL]/");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        NexacroResourceManager.createInstance(this);
        NexacroResourceManager.getInstance().setDirect(false);

        Intent intent = getIntent();
        if(intent != null) {
            String bootstrapURL = intent.getStringExtra("bootstrapURL");
            String projectUrl = intent.getStringExtra("projectUrl");
            if(bootstrapURL != null) {
                setBootstrapURL(bootstrapURL);
                setProjectURL(projectUrl);
            }
        }

        super.onCreate(savedInstanceState);
    }

    @Override
    public void setContentView(View view) {
        super.setContentView(view);
    }

    @Override
    protected void onDestroy() {
        new UserNotify(this);
        super.onDestroy();
    }
}

MainActivity 클래스 선언시 중요한 부분은 Bootstrap URL과 Project URL의 설정입니다. NexacroUpdatorActivity는 이 정보를 바탕으로 앱 업데이트를 진행하고 실행합니다.

Bootstrap URL 설정은 start_android.json 파일이 배치된 서버의 URL 경로를 setBootstrapURL 메소드를 사용하여 설정합니다.

setBootstrapURL("http://[Bootstrap File URL]/start_android.json");

Project URL 설정은 제너레이트된 프로젝트 소스가 배치된 서버의 URL 경로를 setProjectURL 메소드를 사용하여 설정합니다. URL의 끝에는 "/"를 붙여줍니다.

setProjectURL("http://[Generated Project URL]/");

다음은 onDestroy 메소드에서 UserNotify 객체의 인스턴스를 생성해 줍니다. UserNotify는 넥사크로의 자바스크립트에서 안드로이드의 자바로 메시지 전달을 담당하는 이벤트 객체입니다.

protected void onDestroy() {
    new UserNotify(this);
    super.onDestroy();
}

UserNotify 클래스는 사용자가 직접 작성해야 합니다. 자세한 구현 방법은 자바스크립트에서 자바로 메시지 전달에서 UserNotify 단락을 참조하시기 바랍니다.

17.10.5앱의 종료

현재 실행중인 앱을 종료시키려면 넥사크로 Application 객체의 exit 메소드를 사용합니다. 넥사크로 앱은 기본적으로 수행중에 NexacroActivity라는 단일 Activity 구조로 동작하므로 넥사크로에서 exit 메소드를 호출하여 종료해 버리면 앱이 종료됩니다.

다음은 사용자가 모바일 장치의 뒤로가기 버튼을 터치했을 때 자바스크립트에서의 처리 함수입니다.

this.SetAlarm_ondevicebuttonup = function(obj:nexacro.Form,e:nexacro.DeviceButtonEventInfo)
{
    //back button
    if(e.button == 2)
    {    
        if(this.confirm("Do you want to quit?", "EXIT", "question"))
        {            
            var objApp = nexacro.getApplication();
            objApp.exit();
        }
    }
};

17.10.6앱을 백그라운드로 전환

사용자가 앱을 나가려 할 때 앱을 완전히 종료시키지 않고 백그라운드로 전환할 수 있습니다. 이는 작업중이던 화면을 유지해야 할 때 유용합니다. 앱의 백그라운드 전환은 크게 intent를 사용해 홈 화면으로 이동하는 방법과 moveTaskToBack 메소드를 사용하는 두 가지 방법이 있습니다. 여기서는 좀 더 간편한 방법인 intent를 이용한 방법을 사용합니다.

다음은 사용자가 모바일 장치의 Back 버튼을 눌렀을 때 자바스크립트에서의 처리 함수입니다. userNotify 메소드를 사용해 자바 코드 쪽으로 앱을 백그라운드로 전환하도록 메시지를 전달합니다. userNotify 메소드의 인수로 설정한 nNotifyID와 strMessage 값은 앱 개발시 임의로 지정한 값으로 필요시 변경해서 사용할 수 있습니다.

this.SetAlarm_ondevicebuttonup = function(obj:nexacro.Form,e:nexacro.DeviceButtonEventInfo)
{
    //back button
    if(e.button == 2)
    {
        if(this.confirm("Do you want to quit?", "EXIT", "question"))
        {    
            var strParams = "ACTIVITYHIDE";    
            var objEnv = nexacro.getEnvironment();
            objEnv.userNotify(901, strParams);    
        }
    }
};

다음은 자바스크립트에서 userNotify 메소드를 호출했을 때 자바에서 발생한 이벤트를 처리하는 onUserNotify 이벤트 함수의 일부입니다. 자바스크립트에서 보낸 메시지를 확인하여 intent를 설정하여 장치의 홈 화면으로 이동하는 작업을 수행합니다.

@Override
public void onUserNotify(int nNotifyID, String strMessage) {
    switch(nNotifyID)
    {    
        case 901:   //ACTIVITY hidden 처리, 홈 화면으로 이동

            Intent intent = new Intent();
            intent.setAction(Intent.ACTION_MAIN);
            intent.addCategory(Intent.CATEGORY_HOME);
            this.context.startActivity(intent);
            break;

Intent의 액션을 "ACTION_MAIN"으로 설정하고, 카테고리를 "CATEGORY_HOME"으로 설정한 후 activity를 전환하는 startActivity 메소드를 호출하면 모바일 장치의 홈 스크린 화면으로 전환됩니다.

17.10.7앱 리소스 사용

안드로이드 앱 배포시 사용하는 apk 파일은 앱의 수행에 관련된 코드뿐 아니라 이미지, 오디오, 문자열 등의 리소스도 담을 수 있습니다. 안드로이드 스튜디오에서 포함시킨 리소스는 SDK 빌드 도구가 고유한 정수형 ID를 자동으로 부여하여 관리합니다. 이 리소스 ID를 사용하면 앱에서 특정 리소스에 접근하여 사용하는 것이 가능해집니다.

public static final class raw {
  public static final int jetaime=0x7f0b0000;
  public static final int ouu=0x7f0b0001;
}

예를 들어, 오디오 파일인 jetaime.mp3를 res/raw/ 디렉터리에 추가했다면 위와 같이 SDK 도구가 앱 빌드시 자동으로 리소스 ID를 생성해 줍니다.

그림 17-53android_res_raw

사용자가 앱에서 리소스를 사용하려면 복잡한 정수형 ID값을 찾아서 입력할 필요 없이 다음과 같은 형식으로 사용이 가능합니다.

R.raw.jetaime

R은 R 클래스를 의미하고, raw는 리소스 유형, jetaime은 리소스 이름을 의미합니다. 이 형식은 프로젝트의 리소스 디렉터리 구조와 같습니다. 이미지, 문자열 등의 다른 종류의 리소스들에 대해서도 같은 형식으로 사용이 가능합니다.

다음은 실제 코드에서 리소스에 접근하는 예를 보여줍니다. 앱 리소스의 오디오 파일을 MediaPlayer에서 재생하기 위해 설정하는 예입니다.

mediaPlayer = MediaPlayer.create(this, R.raw.jetaime);

17.10.8Calendar 오브젝트 시간 설정

알람 설정시 사용자로부터 입력받은 시간을 calendar 오브젝트를 사용하여 설정하면 편리합니다. 일반적으로는 그냥 입력받은 값을 설정하면 되지만 월과 초 설정은 유의해야 합니다.

월 설정의 경우는 Calendar.MONTH에 값을 설정해줄 때 원하는 월-1을 설정해야 합니다. 이는 Calendar.MONTH 값의 범위가 0~11까지이기 때문으로 설정할 월에서 -1을 해줘야 원하는 월값을 입력할 수 있습니다.

초 설정의 경우는 Calendar.SECOND를 0으로 설정합니다. 일반적으로 알람 설정시 초 단위까지는 설정하지 않으므로 Calendar.SECOND 값 설정없이 알람을 설정하면 알람을 설정했던 초 값이 자동으로 설정되어 설정한 분(Calendar.MINUTE)의 정각에 알람이 울리지 않게 됩니다.

Calendar calendar = Calendar.getInstance();

calendar.set(Calendar.YEAR, intYear);
calendar.set(Calendar.MONTH, intMonth-1);
calendar.set(Calendar.DATE, intDay);
calendar.set(Calendar.HOUR_OF_DAY, intHour);
calendar.set(Calendar.MINUTE, intMin);
calendar.set(Calendar.SECOND, 0);

alarm_manager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);

17.10.9AlarmManager를 사용한 알람 설정, 해제, 취소, 확인

앱 개발시 원하는 시간에 특정 작업을 수행하도록 해야 하는 경우가 있습니다. 안드로이드 시스템에서는 원하는 시간에 시스템으로부터 알림을 받을 수 있는 알람 기능을 AlarmManager 컴포넌트를 통해 제공합니다. AlarmManager를 사용해 시스템에 알람을 등록해 놓으면 설정된 시간에 맞춰 시스템이 메시지를 보내 앱에 등록된 브로드캐스트 리시버에 알려줍니다. 사용자는 브로드캐스트 리시버를 통해 정해진 시간에 수행해야 할 작업을 수행합니다.

AlarmManager를 사용한 알람 설정은 다음과 같이 진행됩니다.

// 인텐트를 수신할 BroadcastReceiver 클래스 설정
Intent alarmIntent = new Intent([현재 activity].this, [intent를 수신할 class]);

// 인텐트를 통해 전달할 값 설정
alarmIntent.putExtra("state","ALARMON");

// BroadcastReceiver를 시작하는 인텐트 생성
PendingIntent pendingIntent = PendingIntent.getBroadcast([현재 activity].this,
        0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);

// calendar 객체에 알람 시간 설정
Calendar calendar = Calendar.getInstance();

calendar.set(Calendar.YEAR, intYear);
calendar.set(Calendar.MONTH, intMonth-1);
calendar.set(Calendar.DATE, intDay);
calendar.set(Calendar.HOUR_OF_DAY, intHour);
calendar.set(Calendar.MINUTE, intMin);
calendar.set(Calendar.SECOND, 0);

// AlarmManager 객체 생성
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
        
// 알람 설정
alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);

설정한 알람을 해제(종료)시키려면 다음과 같이 AlarmManager의 cancel 메소드를 사용합니다. 그리고 RingtonePlayingService에서 알람음 재생을 멈추기 위해 "ALARMOFF" 상태값을 인텐트에 담아 전달합니다.

//알람매니저 cancel
alarmManager.cancel(pendingIntent);

//필요한 경우 인텐트를 통해 값 전달
alarmIntent.putExtra("state","ALARMOFF");

//서비스로 ALARMOFF 전달하여 알람음 재생 stop
this.context.sendBroadcast(alarmIntent);

설정된 알람을 취소시키려면 다음과 같이 알람 해제와 더블어 브로드캐스트 리시버인 AlarmReceiver의 등록을 해제시켜줘야 합니다.

Intent cancelIntent = new Intent(this.context, AlarmReceiver.class);
PendingIntent cancelSender = PendingIntent.getBroadcast(this.context, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT);

if(cancelSender != null) {
    alarmManager.cancel(cancelSender);
    cancelSender.cancel();

    strMsg = "설정된 알람을 취소했습니다.";
}
else {
    strMsg = "취소할 알람이 없습니다.";
}

알람이 설정되어 있는지 확인하려면 브로드캐스트 리시버가 등록되어 있는지 확인해줍니다.

Intent checkIntent = new Intent(this.context, AlarmReceiver.class);
PendingIntent checkSender = PendingIntent.getBroadcast(this.context, 0, checkIntent, PendingIntent.FLAG_NO_CREATE);

if(checkSender == null) {
    strMsg = "설정된 알람이 없습니다.";
}
else {
    strMsg = "설정된 알람이 있습니다.";
}

17.10.10Service 수행과 종료

서비스는 사용자와 상호작용하는 Activity와는 달리 백그라운드에서 동작하는 컴포넌트입니다. Activity의 상태와 관계없이 어떤 작업을 수행해야 할 경우에 사용합니다. 예를 들어, 음악 재생이나 알람음 재생같은 경우 앱이 포그라운드에서 열려있지 않거나 종료된 상태에서도 기능이 계속 수행되어야 합니다. 이런 경우에 서비스를 사용합니다.

서비스를 수행하려면 우선 서비스 클래스의 intent 객체를 다음과 같이 생성합니다. RingtonePlayingService 클래스는 미디어 파일을 재생하는 MediaPlayer 서비스로 Service 클래스를 상속받아 구현합니다.

// RingtonePlayingService 서비스 인텐트 생성
Intent service_intent = new Intent(context, RingtonePlayingService.class);

생성된 RingtonePlayingService intent는 startService 메소드를 사용해 서비스로 실행할 수 있습니다. 한가지 유의할 점은 안드로이드 API 버전에 따라 서비스 실행시 사용하는 방식에 차이가 있다는 것입니다. Oreo(8.0, API level 26)에서는 서비스의 백그라운드 실행을 금지하기 때문에 서비스 실행을 포그라운드(startForegroundService)로 실행해야 하며 그 이전 버전에서는 백그라운드(startService)로 실행합니다. 따라서 버전을 체크하여 현재 장치에서 동작중인 SDK 버전이 오레오(8.0, API level 26) 버전보다 높으면 startForegroundService 메소드를 호출하여 서비스를 시작하고, 낮으면 startService 메소드를 호출하여 서비스를 시작합니다.

// RingtonePlayingService 서비스 시작
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
    this.context.startForegroundService(service_intent);
}else{
    this.context.startService(service_intent);
}

필요에 따라 서비스로 상태값과 같은 데이터를 전달해야 할 경우에는 intent 객체를 사용할 수 있습니다. intent로부터 값을 가져올 때는 getExtras 메소드를 사용하고 값을 저장할 때는 putExtras 메소드를 사용합니다.

// intent로부터 전달받은 상태값
String strAlarmState = intent.getExtras().getString("state");

// RingtonePlayinService로 상태값 전달
service_intent.putExtra("state", strAlarmState);

17.10.11미디어 플레이어를 사용한 알람음 재생

안드로이드 시스템에서는 미디어를 재생하기 위해 MediaPlayer 오브젝트를 제공합니다. 로컬 스토리지, 앱의 리소스를 통한 비디오, 오디오 파일 재생과 URL을 통한 스트리밍 재생이 가능합니다.

앱 리소스 파일을 MediaPlayer로 재생하는 가장 간단한 방법은 다음과 같습니다. 먼저 다음과 같이 리소스 id와 함께 create 메소드를 호출하여 객체를 생성합니다. 그런 후 start 메소드를 호출하면 리소스에 해당하는 미디어 파일의 재생이 시작됩니다.

MediaPlayer mediaPlayer;

mediaPlayer = MediaPlayer.create(this, R.raw.jetaime);

mediaPlayer.start();

재생을 일시 정지시키려면 pause 메소드를 사용합니다. 일시 정지 상태에서 다시 재생을 시작하려면 start 메소드를 호출합니다.

mediaPlayer.pause();

MediaPlayer의 재생을 멈추려면 stop 메소드를 호출합니다.

mediaPlayer.stop();

만일 다른 미디어를 재생하려거나 초기 상태로 되돌리고 싶으면 reset 메소드를 사용합니다.

mediaPlayer.reset();

더 이상 미디어 재생이 필요없어 MediaPlayer 객체를 완전히 제거할 땐 release 메소드를 사용합니다.

mediaPlayer.release();

MediaPlayer 오브젝트에 관한 자세한 정보는 다음 링크를 참조하십시오.

https://developer.android.com/reference/android/media/MediaPlayer

17.10.12안드로이드 API 레벨(SDK 버전) 관리

넥사크로플랫폼에서 지원하는 안드로이드 최소 지원 사양은 4.4.2(KitKat) 버전입니다. 따라서 minimum SDK 설정시 API 레벨 19 이상을 지원하는 SDK를 사용해야 합니다.

안드로이드 환경에서 앱 개발시에는 API 레벨의 관리가 중요합니다. 각 버전에 따라 지원하는 장치, 기능 및 보안 등에 제약이 있으므로 이러한 것들을 종합적으로 고려해야 합니다. 다음은 안드로이드 API 레벨별 누적 분포를 보여줍니다.

그림 17-54안드로이드_플랫폼_버전별_누적_분포현황_2

안드로이드 스튜디오에서는 API 레벨에 따라 SDK 버전이 나뉩니다. 현재 사용중인 SDK 버전을 변경하려면 build.gradle에서 compileSdkVersion 값을 설정합니다. 그러면 새로운 SDK에 맞춰 환경을 구성하는 gradle sync 작업이 수행되는며 재컴파일이 수행됩니다. 만일 새로 설정한 compileSdkVersion의 SDK 버전이 PC에 설치되어 있지 않으면 자동으로 다운로딩 및 인스톨이 진행됩니다.

/* build.gradle */
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.demo.wakemeup"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        .
        .

안드로이드에서는 compileSdkVersion, minSdkVersion, targetSdkVersion 설정을 통하여 안드로이드 API 레벨간 호환성을 보장하도록 합니다.

compileSdkVersion

프로젝트를 컴파일 할 때 사용할 SDK의 버전입니다. 즉, 앱이 지원할 수 있는 가장 높은 SDK 버전을 말합니다. 이 버전은 가능한 최신 버전으로 유지하는게 앱의 안정성에 좋습니다. compileSdkVersion은 targetSdkVersion보다 높거나 같아야 합니다.

minSdkVersion

앱이 실행되는데 필요한 최소 SDK 버전을 설정합니다. minSdkVersion보다 낮은 API 버전의 기능은 사용할 수 없습니다. 구글 플레이 서비스에서 사용자의 기기에 앱을 설치할 수 있을지 결정하는 요소로 안드로이드 시스템의 API 레벨이 이 값보다 낮은 경우 앱 설치가 되지 않습니다.

이 값은 반드시 설정해야 하는데 만약 설정하지 않았을 경우에는 1로 설정됩니다.

targetSdkVersion

앱이 지원하는(테스트된) 가장 높은 SDK 버전을 설정합니다. minSdkVersion 이상에서 지원하는 기능을 사용하고 싶을 때 해당 버전을 설정해줍니다. 이 버전에 따라 앱의 동작 로직이 변경되며 안드로이드의 버전간의 호환성을 제공할 수 있습니다. 예를 들어, 안드로이드 마시멜로 6.0(API 23)에서 도입된 런타임 퍼미션 모델은 targetSdkVersion을 API 23으로 설정해야 적용됩니다.

이 값을 선언하지 않을시 기본 값은 minSdkVersion 값이 됩니다.

각 SDK 버전의 설정 가능 범위는 다음과 같습니다.

minSdkVersion <= targetSdkVersion <= compileSdkVersion

minSdkVersion, targetSdkVersion 값은 apk가 빌드되는 과정에서 AndroidManifest.xml에 <uses-sdk> attribute로 포함됩니다.

종속성이 있는 라이브러리가 앱에서 사용중일 때 라이브러리 버전이 compileSdkVersion과 맞지 않으면 에러가 발생합니다. 이런 경우에는 build.gradle의 dependencies에서 라이브러리의 버전을 SDK 버전에 맞게 수정해줍니다.

/* build.gradle */
        .
        .
dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'com.android.support:appcompat-v7:26.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
    implementation files('libs/nexacro17.android.jar')
}