[SPECIAL REPORT] VC++로 안드로이드 앱 개발하기

비주얼 스튜디오(Visual Studio) 2015부터 비주얼 C++(이하 VC++)에도 크로스 플랫폼 환경이 추가돼, 이제 한번 개발로 다양한 플랫폼에서 실행되는 소프트웨어를 개발할 수 있게 됐다. 아직까지는 안드로이드나 iOS 관련 프로젝트만 생성 가능하나 테크데이즈 2015(Techdays 2015)에서 발표됐듯 조만간 리눅스 응용 프로그램 개발이 추가될 예정이다. iOS 앱 개발의 경우 맥OS X와 Xcode와 같은 추가적 개발 환경을 요하므로 이 글에서는 별다른 추가 요구사항이 없는 안드로이드 앱 개발에 대해 주로 살펴본다.

[toggle title=”필자 소개” load=”show”]
김성엽|마이크로소프트 비주얼 C++ MVP이자 팁스웨어(Tipsware)의 대표. 적성도, 취미도, 직업도 개발인 24년차 C++ 개발자다. 프로그래밍 없이 하루도 못산다. 직접 만들어 쓰고, 사람들과 함께 새로운 기술을 배우는 것을 즐긴다. 매주 대학생을 대상으로 한 지식나눔을 실천하며 IT 분야의 황무지를 개척하고 있다.
[/toggle]

“The NDK(Native Development Kit) is a toolset that allows you to implement parts of your app using native-code languages such as C and C++.” 안드로이드 개발자 사이트(developer.android.com)의 NDK 소개처럼 NDK는 C/C++와 같은 네이티브 언어로 안드로이드 앱 개발이 가능한 툴셋이다. 안드로이드 앱 개발자 대부분은 안드로이드가 제공하는 프레임워크 API로 앱을 개발한다. 그러나 이러한 프레임워크가 개발가 필요로 하는 모든 API를 제공하지 못할 뿐 아니라, 있더라도 기대 성능에 못미치기도 한다. 이러한 경우 C/C++로 앱의 완성도나 성능 향상을 위해 API를 직접 개발할 수 있다. 이와 관련된 도구 집합이 바로 NDK다. 익히 알려져 있듯 안드로이드는 자바 기반이기에 C/C++ 개발자가 안드로이드 프레임워크를 익혀 이클립스(Eclipse)나 안드로이드 스튜디오와 같은 새로운 개발도구로 앱을 개발하는 것은 지금까지 해온 것을 두고 새로이 옮겨가는 일이었다. 이러한 선택은 도전에 가까웠다. 하지만 NDK를 사용하면 자바와 병행하거나 자바를 전혀 쓰지 않고도 안드로이드 앱을 개발할 수 있어 C/C++ 개발자에게 좋은 절충안이 될 수 있다. 그런데도 C/C++ 개발자들이 NDK를 적극 이용하지는 않고 있다. C/C++로 개발 가능하다는 이점만 있을 뿐 기존 안드로이드 개발 환경이 필요할 뿐 아니라 개발 도구도 기존 자바의 것을 써야 하기 때문이다.

NDK는 2009년 6월 처음 소개됐지만 이를 아는 사람은 거의 없다. 그간 얼마나 외면을 받아온 것인지 짐작할 수 있는 대목이다. 그런데 비주얼 스튜디오 2015의 비주얼 C++에 크로스 플랫폼 환경이 추가되면서 NDK를 이용할 수 있게 됐다. 이제 NDK는 새로운 전환점을 맞이하게 된 것이다. 비주얼 C++ 개발자가 기존 개발 환경을 바꾸지 않고, 단지 몇 가지 개념만 익히면 안드로이드 네이티브 앱 개발이 가능해졌다. 그리고 이는 다양한 개발 경력을 가진 C/C++ 개발자가 써오던 소스 코드를 안드로이드 앱에 재사용할 수 있다는 것을 의미한다.

비주얼 스튜디오 2015와 크로스 플랫폼 설치
특정 소프트웨어(SW)가 기반이 다른 이기종 운영체제(OS)에서 공통으로 쓰이는 것을 크로스 플랫폼이라고 한다. 그러나 이 말은 단순히 하나의 실행파일이 여러 플랫폼에서 실행되는 것뿐 아니라 기술과 개발환경이 여러 OS를 지원하는가도 포함하는 포괄적 의미를 가지고 있다. 예컨대 하나의 개발 환경에서 하나 이상의 개발 언어로 두 가지 이상의 플랫폼에서 사용 가능한 SW를 만드는 것을 크로스 플랫폼이라고 한다. 비주얼 스튜디오에서 크로스 플랫폼 사용은 최신 버전인 2015에서만 이용 가능하다. 기존에는 상용 비주얼 스튜디오에서만 사용 가능했으나 오픈소스 프로젝트, 학술연구, 교육 등의 목적이라면 무료로 쓸 수 있는 비주얼 스튜디오 커뮤니티 버전을 써도 된다. 참고로 커뮤니티 버전은 프로패셔널 버전과 동일하다. 비주얼 스튜디오 2015의 설치 항목은 이전 대비 더 다양해졌는데, 은 비주얼 C++의 크로스 플랫폼 사용을 위한 체크 예다. 비주얼 스튜디오에 향후 추가될 기능이나 호환성을 생각한다면 윈도우10을 쓰는 게 좋다. 참고로 윈도우 홈 에디션의 경우 하이퍼V(Hyper-V)를 지원하지 않아 안드로이드 에뮬레이터를 사용할 수 없다. 따라서 자신의 PC에 비주얼 스튜디오를 설치하기 전에 PC의 OS부터 확인해야 한다.

20151217701<그림 1> 크로스 플랫폼을 위한 비주얼 스튜디오 2015의 설치 옵션

비주얼 스튜디오의 안드로이드 에뮬레이터
비주얼 스튜디오 2015에 포함된 안드로이드 에뮬레이터는 구글 에뮬레이터보다 반응속도가 더 빠르다. 그러나 고정 크기의 물리 메모리를 사용하기 때문에 PC의 시스템 메모리가 4GB보다 작으면 에뮬레이터가 실행되지 않을 수 있다. 또 구글의 안드로이드 이미지를 그대로 사용하기 때문에 안드로이드 킷캣(KitKat), 즉 4.4 이하 버전에서는 한글을 지원하지 않는다. 만약 한글이 꼭 필요하다면 에뮬레이터에 한글 폰트를 직접 추가해야 한다. 다행히 안드로이드 5.0부터는 한글이 공식 지원된다.

새 프로젝트에 추가된 크로스 플랫폼 관련 템플릿
비주얼 스튜디오 2015를 설치하고 [새 프로젝트] 메뉴를 선택하면 처럼 비주얼 C++에 항목에 [플랫폼 간]이라는 새로운 분류가 있다. 이 항목을 선택하면 크로스 플랫폼 개발과 관련된 9가지 항목을 선택할 수 있다. 이 항목들은 완전히 구별된 개념이 아니라서 처음 템플릿을 선택했더라도 같은 항목들끼리는 설정 값이나 프로젝트 구성을 수정해 다른 형태의 프로젝트 속성으로 변경할 수 있다. 따라서 프로젝트 생성과 관련해서 템플릿 선택을 고민할 필요가 없다.

20151217702<그림 2> 크로스 플랫폼용 템플릿

● Native-Activity 응용 프로그램(Android) : C/C++ 언어로 앱을 만들 때 사용하는 프로젝트다. 자바를 배운 적이 없고 안드로이드 시스템에 대한 이해가 부족하더라도 이 항목을 사용하면 안드로이드 앱을 만들 수 있다.
● 기본 응용 프로그램(Android) : 안드로이드 프레임워크 기반의 앱 개발 시 사용하는 프로젝트다. 자바 기반의 소스가 생성된다. NDK로 API 함수를 개발하고 JNI를 사용해 자바 코드와 연동하는 테스트를 할 때 이 항목을 선택해 프로젝트를 생성하면 된다.
● 동적 공유 라이브러리(Android) : 자바 기반 안드로이드 앱에서 사용할 API 함수를 만들기 위한 프로젝트다. NDK를 사용해 C/C++ 언어로 코드를 작성하면 된다.
● Shared Library(Android, iOS) : 크로스 플랫폼 환경을 구축할 때 사용한다. 이 항목을 사용하면 특정 소스를 가지고 iOS와 안드로이드용 동적 라이브러리를 동시에 개발할 수 있다. 이 템플릿을 사용해 솔루션을 생성하면 처럼 세 가지 항목의 솔루션이 생성된다. Tipsware SharedLibrary.Android와 TipswareSharedLibrary.iOS로 만들어질 동적 라이브러리는 TipswareSharedLibrary.Shared에 있는 소스 파일과 헤더파일을 참조해서 빌드된다.

20151217703<그림 3> Shared Library

● OpenGL ES 응용 프로그램(Android, iOS) : iOS와 안드로이드 앱에서 화면 출력 시 두 플랫폼 모두 OpenGL ES를 지원하므로 OpenGL ES를 사용하는 코드를 공통 소스로 구성하고, 각 앱에서 참조해 빌드하는 프로젝트 형식이다. 이 프로젝트 형식을 사용하면 안드로이드 앱과 iOS 앱이 동시에 만들어진다. 개념은 Shared Library와 유사하지만 이 형식은 앱을 만드는 프로젝트이고 Shared Library는 동적 라이브러리를 만드는 프로젝트라는 차이가 있다.
● 빈 패키징 프로젝트(iOS) : iOS용 비어있는 프로젝트를 생성한다.
● 정적 라이브러리(Android) : 안드로이드 앱에서 사용할 라이브러리 파일을 생성한다. 일반적으로 C/C++ 언어를 사용할 안드로이드용 라이브러리를 개발 시 주로 사용한다.
● 정적 라이브러리(iOS) : iOS 앱에서 사용할 라이브러리 파일을 생성한다.
● 메이크 파일 프로젝트(Android) : 다른 편집 툴로 소스 코드를 작업하고 빌드하거나, 여러 개의 프로젝트를 동시에 빌드하는 데 필요한 메이크 파일용 프로젝트를 생성한다.

비주얼 C++로 안드로이드 네이티브 앱 개발
안드로이드 네이티브 앱은 [새 프로젝트]에서 [Native-Activity 응용 프로그램(Android)]을 선택해 만들 수 있다. C/C++ 언어로만 작성된 안드로이드 앱을 개발할 수 있다. Tipssoft 라는 솔루션을 만들면 처럼 2개의 프로젝트가 솔루션에 추가된다. Tipssoft.NativeActivity 프로젝트는 네이티브 앱 개발 소스고, Tipssoft.Packaging 프로젝트는 동적 라이브러리 형태로 컴파일된 네이티브 앱을 안드로이드에서 실행 가능한 .apk 형태로 만들어 준다. 빌드 순서는 Tipssoft.NativeActivity, Tipssoft.Packaging 순이다.

20151217704<그림 4> 안드로이드 프로젝트

● pch.h : 기존 비주얼 C++의 [미리 컴파일된 헤더]와 같은 개념이다. 따라서 추가로 사용할 헤더 파일이 있으면 여기에 추가해주면 된다. 하지만 기존에 제공되던 stdafx.h와 달리 프로젝트에 포함된 각 소스 파일에서는 #include “pch.h”를 입력하지 않아도 자동으로 include가 된다.
● android_native_app_glue.h와 android_native_app_glue.cpp : 안드로이드 네이티브 액티비티(Activity)가 만들어지고 실행 흐름에 따라 호출될 콜백(callback) 함수들이 여기에 정의돼 있다. 네이티브 앱 개발이 능숙해지기 전에는 여기를 손볼 일이 거의 없다.
● main.cpp : 앱 개발 관련해 실제로 작업을 해야 할 파일이다. 이 파일의 android_main 함수가 메인 프로시저다. 사용자의 터치, 버튼 클릭, 센서 값 그리고 화면 출력과 관련된 작업이 필요할 때 시스템에 의해 호출될 함수들이 여기에 생성돼 있다.
● AndroidManifest.xml : 액티비티 속성에 대한 설정 파일이다. 이 파일에는 액티비티의 이름, 형식 그리고 동작 특성에 대한 설정 값을 추가하거나 변경할 수 있으며 실행 중에 가질 수 있는 로컬 리소스(Local Resource)에 대한 권한을 명시할 수 있다. 예컨대 이 앱이 네트워크를 사용해야 할 경우 권한을 요청하는 속성 값을 이 파일에 명시하면 된다.
● build.xml : 빌드 시에 참조할 SDK나 추가적인 라이브러리 또는 빌드 방식에 대한 설정을 위한 파일이다. 특별한 경우가 아니라면 변경할 필요가 없다.
● project.properties : 패키징 관련 속성을 명시하는 파일이다.

main.cpp의 함수 구성과 역할
안드로이드는 사용자의 조작이나 시스템의 메모리 부족과 같은 이유로 프로세스가 임의로 종료되기도 한다. 만약 프로세스가 갑자기 종료되면 해당 프로세스의 데이터도 다 삭제되기 때문에 안드로이드 앱은 자신의 상태를 저장해둔다. 다음은 이 글에서 제작할 예제 앱의 상태가 정의된 구조체다.
20151217709

네이티브 앱의 실행 흐름
의 진한 회색 사각형은 android_native_app_glue.c 에 있는 함수들이고, 연한 회색 사각형은 main.cpp에 있는 함수들이다. 안드로이드가 생소하다면 연한 회색 사각형을 중심으로 구조를 파악하면 된다.
20151217705<그림 5> 네이티브 앱의 실행 흐름

● 화면 출력
안드로이드 네이티브 앱은 화면 출력 루틴에 OpenGL ES를 사용한다. OpenGL ES는 하드웨어에 독립적인 API들로 구성돼 있어 실제 하드웨어 출력할 때에는 해당 하드웨어에 최적화된 EGL(Embedded-System Graphics Library) API를 사용한다.

● 화면 좌표계
폭과 높이가 2×2인 사각형이고 수학에서 사용하는 좌표계와 같다. 이 영역은 상단의 태스크 바를 포함하므로 좌표 계산에 주의해야 한다.

● 색상 정보
Red, Green, Blue, alpha 값으로 구성되며, 각각은 4바이트 크기의 실수다. 0.0 ~ 1.0 사이의 값을 가진다.
20151217706<그림 6> 화면 출력과 화면 좌표계 이용법

사용자 입력 처리하기
20151217708<그림 8> 사용자 입력 처리
스마트폰을 터치하거나 버튼을 클릭하면 이벤트가 발생한다. 이 이벤트는 main.cpp에 있는 engine_handle_input 함수로 전달된다(그림8 참조).
따라서 engine_handle_input 함수가 호출됐다는 뜻은 사용자가 어떤 입력을 했다는 것이다. 이 입력 행위에 대한 구체적인 정보는 AInputEvent_getType 함수를 호출해 얻을 수 있다. 이 함수는 AINPUT_EVENT_TYPE_KEY(1) 또는 AINPUT_EVENT_TYPE_MOTION(2) 값을 반환하는데 각각은 키 클릭, 모션 관련 입력을 의미한다. 따라서 리스트2처럼 코드를 구성하면 입력 이벤트를 구분할 수 있다.
20151217710

사용자가 모션 형식의 정보를 입력한 경우 어떤 행위인지 좀 더 구체적으로 알고 싶다면 AMotionEvent_getAction 함수를 이용하면 된다. 이 함수는 AKEY_EVENT_ACTION_DOWN(0), AKEY_EVENT_ACTION_UP(1), AKEY_EVENT_ACTION_MULTIPLE(2) 값을 반환하는데 순서대로 버튼 클릭, 버튼 클릭 해제, 멀티터치 입력임을 뜻한다. 만약 이 세 가지 값이 모두 아닌 경우 드래그를 한 것이다.

20151217711

모션 이벤트가 발생한 화면상의 위치 값은 AMotionEvent_getX, AMotionEvent_getY 함수로 알아낼 수 있다. 그런데 이 함수를 통해 얻는 좌표 값은 장치의 해상도에 따른 좌표 값이라서 OpenGL ES가 사용하는 좌표 값과 다르다. 현재 사용하는 장치의 해상도는 engine 구조체의 width, height 변수에 저장돼 있다.

● 정리| 조수현

답글 남기기