안드로이드 NDK 시작하는 법
Korean (한국어) translation by Dae-yeop Lee (you can also view the original English article)
안드로이드 스튜디오 2.2가 출시되면서 C++ 코드가 포함된 안드로이드 애플리케이션을 개발하는 것이 그 어느 때보다 쉬워졌습니다. 이 튜토리얼에서는 자바 클래스에서 함수를 사용할 수 있는 네이티브 C++ 라이브러리를 만들기 위해 보통 NDK라고 하는 안드로이드 네이티브 개발 키트(Native Development Kit)를 사용하는 방법을 보여드리겠습니다.
준비물
이 튜토리얼을 따라할 수 있으려면 다음과 같은 준비물이 필요합니다.
- 최신 버전의 안드로이드 스튜디오
- C++ 구문에 대한 기본적인 이해
1. 왜 네이티브 코드를 작성하는가?
일반적으로 여러분은 자바만 사용해서 안드로이드 애플리케이션을 개발할 것입니다. C++ 코드를 추가하는 것은 복잡도를 현격히 높이고 애플리케이션의 이식성도 떨어뜨립니다. 그럼에도 네이티브 코드를 작성하는 몇 가지 이유가 있습니다.
- 성능을 최대화하기 위해: 비즈니스 로직의 CPU 집약적인 부분을 C++로 구현해서 안드로이드 애플리케이션의 성능을 약간 향상시킬 수 있습니다.
- 고성능 API를 사용하기 위해: Vulkan Graphics나 OpenSL ES 같은 API 사양의 구현이 NDK의 일부로 포함돼 있습니다. 따라서 안드로이드 게임 개발자들은 NDK를 사용하는 경향이 있습니다.
- 널리 사용되는 C/C++ 라이브러리를 사용하기 위해: 자바에는 상응하는 것이 없는 수많은 C/C++ 라이브러리가 있습니다. 안드로이드 앱에서 이러한 라이브러리를 이용하려면 NDK를 사용하는 것이 좋습니다.
- 코드를 재사용하기 위해: 플랫폼별 종속성을 포함하지 않는 이상 C++로 작성된 코드는 보통 안드로이드 및 iOS 애플리케이션에서 최소한의 변경으로 사용할 수 있습니다. 대규모 애플리케이션을 개발 중이고 iOS와 안드로이드 플랫폼을 모두 지원하려는 경우 C++을 이용하면 생산성이 향상될 수 있습니다.
2. 새 프로젝트 만들기
안드로이드 스튜디오 2.2 이상에서는 프로젝트 생성 마법사를 통해 C++ 코드를 지원하는 새 프로젝트를 신속하게 만들 수 있습니다.
안드로이드 스튜디오를 실행한 후 시작 화면에서 Start a new Android Studio project 버튼을 누릅니다. 다음 화면에서 애플리케이션의 이름을 지정하고 Include C++ Support 필드를 체크합니다.



마법사의 액티비티 생성 화면에서는 Add No Activity 옵션을 선택합니다. 마법사의 마지막 화면에서는 C++ Standard 필드의 값이 Toolchain Default로 설정돼 있는지 확인하고 Finish 버튼을 누릅니다.



안드로이드 NDK와 이 도구가 의존하는 도구는 기본적으로 설치되지 않습니다. 따라서 프로젝트가 생성되면 다음과 같은 오류가 표시됩니다.



오류를 수정하려면 Tools > Android > SDK Manager로 이동한 후 SDK Tools 탭으로 전환합니다.
사용 가능한 개발자 도구 목록에서 CMake와 NDK를 모두 선택하고 Apply 버튼을 누릅니다.



설치가 완료되면 안드로이드 스튜디오를 다시 시작합니다.
3. 네이티브 라이브러리 생성하기
C++를 지원하는 안드로이드 스튜디오 프로젝트에는 cpp라는 소스 코드 디렉터리가 하나 더 있습니다. 짐작하셨겠지만 모든 C++ 파일과 라이브러리는 그 안에 있어야 합니다. 기본적으로 이 디렉터리에는 native-lib.cpp라는 파일이 있습니다. 지금은 모든 C++ 코드를 이 파일 안에 작성하겠습니다.
이 튜토리얼에서는 πr² 공식을 사용해 원의 면적을 계산하는 함수가 포함 된 간단한 네이티브 라이브러리를 만들겠습니다. 이 함수에서는 원의 반지름을 jdouble
로 받아 넓이를 jstring
으로 반환합니다.
먼저 파일에 다음 include
지시문을 추가합니다.
1 |
#include <jni.h> |
2 |
#include <string> |
3 |
#include <math.h> |
jni.h는 다수의 매크로 정의, 타입, 구조체, 함수를 포함하는 헤더 파일로서, NDK로 작업하는 동안 이 모든 것들은 반드시 필요합니다. (JNI는 Java Native Interface의 약자이며, Java Runtime이 네이티브 코드와 통신할 수 있게 해주는 프레임워크입니다.) 여기서 만들 라이브러리에서 jstring
타입을 사용하기 때문에 string 헤더 파일이 필요합니다. math.h 헤더 파일에는 π 값이 들어 있습니다.
기본적으로 C++ 컴파일러는 다형성을 지원하기 위해 코드에서 정의한 모든 함수의 이름을 수정합니다. 이 기능을 종종 이름 맹글링(mangling)이라고도 합니다. 이름 맹글링 때문에 자바 코드에서 C++ 함수를 호출하면 오류가 발생합니다. 오류를 방지하기 위해서는 extern "C"
블록 내에 함수를 정의해서 이름 맹글링을 비활성화할 수 있습니다.
1 |
extern "C" { |
2 |
// Your functions must be defined
|
3 |
// here
|
4 |
}
|
JNI를 통해 접근할 수 있는 C++ 함수의 이름은 다음과 같은 형태여야 합니다.
-
Java_ 접두사가 있어야 합니다.
- 맹글링된 형태의 패키지명(점들이 밑줄로 대체된)을 포함해야 합니다.
- 각 함수가 속한 자바 클래스의 이름을 포함해야 합니다.
또한 함수의 가시성(visibility)을 지정해야 합니다. 이 경우 JNIEXPORT
매크로를 사용하면 됩니다. 현재는 안드로이드에서 아무런 역할도 하지 않습니다만 관례상 대부분의 개발자도 함수 정의에 JNICALL
매크로를 포함하고 있습니다.
다음 코드에서는 calculateArea
라는 함수를 정의합니다. 이 함수는 MainActivity
라는 자바 클래스에서 접근할 수 있습니다.
1 |
JNIEXPORT jstring JNICALL |
2 |
Java_com_tutsplus_mynativeapplication_MainActivity_calculateArea( |
3 |
JNIEnv *jenv, |
4 |
jobject self, |
5 |
jdouble radius |
6 |
) { |
7 |
|
8 |
}
|
참고로 반지름 외에도 이 함수는 자바 타입을 처리하는 데 사용할 수 있는 유틸리티 함수가 담긴 JNIEnv
타입뿐 아니라 MainActivity
인스턴스에 대한 참조에 해당하는 jobject
인스턴스도 받습니다. 물론 이 튜토리얼의 후반부에서는 MainActivity
를 만들 것입니다.
넓이를 계산하는 것은 쉽습니다. M_PI 매크로에 반지름의 제곱을 곱하기만 하면 됩니다.
1 |
jdouble area = M_PI * radius * radius; |
JNI에서 문자열을 처리하는 방법을 알았으므로 이제 넓이를 알려주는 메시지가 포함된 새로운 문자열을 만들겠습니다. 이를 위해서는 sprintf()
함수를 사용하면 됩니다.
1 |
char output[40]; |
2 |
sprintf(output, "The area is %f sqm", area); |
자바는 C++ 문자 배열을 직접 처리할 수 없으므로 함수의 반환형은 jstring
입니다. output
배열을 jstring
객체로 변환하려면 NewStringUTF()
함수를 사용해야 합니다.
1 |
return jenv->NewStringUTF(output); |
이제 C++ 코드가 준비됐습니다.
4. 네이티브 라이브러리 사용하기
이전 단계에서는 calculateArea()
함수가 MainActivity
자바 클래스에 속해야 한다는 것을 확인했습니다. 자바 패키지 이름을 마우스 오른쪽 버튼으로 클릭하고 File > New > Empty Activity를 선택해서 클래스 생성을 시작합니다.
새로 열린 대화상자에서 액티비티의 이름을 MainActivity로 지정합니다. Launcher Activity 옵션이 체크돼 있는지 확인한 후 Finish 버튼을 누릅니다.



네이티브 라이브러리는 사용하기 전에 로드해야 합니다. 따라서 클래스에 static
블록을 추가하고 System
클래스의 loadLibrary()
메서드를 사용해 라이브러리를 로드합니다.
1 |
static { |
2 |
System.loadLibrary("native-lib"); |
3 |
}
|
액티비티 내에서 calculateArea()
C++ 함수를 사용할 수 있으려면 native
메서드로 선언해야 합니다.
1 |
private native String calculateArea(double radius); |
이제 여느 자바 메서드처럼 calculateArea()
메서드를 사용할 수 있습니다. 예를 들어, 다음 코드를 onCreate()
메서드에 추가해서 반지름이 5.5인 원의 넓이를 계산하고 출력할 수 있습니다.
1 |
Log.d(TAG, calculateArea(5.5f)); |
앱을 실행하면 logcat 창에서 다음과 같은 출력 결과를 볼 수 있을 것입니다.



결론
이 튜토리얼에서는 네이티브 C++ 라이브러리를 생성하고 이를 안드로이드 애플리케이션에서 사용하는 방법을 배웠습니다. 네이티브 빌드 프로세스는 기본적으로 NDK에서 지원하는 모든 단일 CPU 아키텍처에 대해 별도의 .so 파일을 생성합니다. 따라서 애플리케이션이 대부분의 안드로이드 기기에서 아무 문제 없이 실행되리라 보장할 수 있습니다.
안드로이드 NDK에 대해 자세히 알아보려면 NDK 가이드를 참조합니다.
그리고 안드로이드 개발에 대한 다른 튜토리얼 및 강좌를 확인해 보세요.