개념

시작하기 전에

이 가이드는 네이티브 프로그래밍과 Android 개발의 핵심 개념을 잘 알고 있는 개발자를 대상으로 합니다.

소개

이 섹션에서는 NDK 작동 방식에 관한 대략적인 설명을 제공합니다. Android NDK는 C 또는 C++('네이티브 코드')를 Android 앱에 삽입할 수 있게 해주는 도구 집합입니다. Android 앱에 네이티브 코드를 사용할 수 있는 기능은 다음 중 하나 이상을 실행하려는 개발자에게 특히 유용할 수 있습니다.

  • 플랫폼 간 앱 이식
  • 기존 라이브러리를 재사용하거나 재사용할 자체 라이브러리 제공
  • 일부 경우, 특히 게임과 같이 계산 집약적인 앱의 성능 향상

작동 방식

이 섹션에서는 Android용 네이티브 애플리케이션 빌드에 사용되는 기본 구성요소를 소개하고 빌드 및 패키징 프로세스를 설명합니다.

기본 구성요소

앱을 빌드할 때는 다음 구성요소를 잘 파악하고 있어야 합니다.

  • 네이티브 공유 라이브러리: NDK에서는 개발자의 C/C++ 소스 코드로부터 네이티브 공유 라이브러리 또는 .so 파일을 빌드합니다.

  • 네이티브 정적 라이브러리: NDK에서는 개발자가 다른 라이브러리에 연결할 수 있는 정적 라이브러리 또는 .a 파일도 빌드할 수 있습니다.

  • 자바 네이티브 인터페이스(JNI): JNI는 자바와 C++ 구성요소 간의 통신 채널 역할을 하는 인터페이스입니다. 이 가이드는 JNI 지식을 전제로 합니다. JNI에 관한 자세한 내용은 자바 네이티브 인터페이스 사양을 참고하세요.

  • Application Binary Interface(ABI): ABI는 앱의 기계어 코드가 런타임 시 시스템과 어떻게 상호작용할지 정확하게 정의합니다. NDK는 이 정의에 따라 .so 파일을 빌드합니다. 다양한 ABI는 서로 다른 아키텍처에 대응합니다. 즉, NDK에는 32비트 ARM, AArch64, x86, x86-64 각각에 맞는 ABI 지원이 포함되어 있습니다. 자세한 내용은 Android ABI를 참고하세요.

  • 매니페스트: 자바 구성요소가 없는 앱을 작성하는 경우 매니페스트NativeActivity 클래스를 선언해야 합니다. 클래스 선언 방법에 관한 자세한 내용은 native_activity.h 인터페이스 사용을 참고하세요.

흐름

Android용 네이티브 앱 개발을 위한 일반적인 흐름은 다음과 같습니다.

  1. 자바로 구현할 부분과 네이티브 코드로 구현할 부분을 결정하여 앱을 디자인합니다.

  2. 다른 Android 프로젝트에서와 마찬가지로 Android 앱 프로젝트를 만듭니다.

  3. 네이티브 전용 앱을 작성하는 경우 AndroidManifest.xmlNativeActivity 클래스를 선언합니다. 자세한 내용은 네이티브 액티비티 및 애플리케이션을 참고하세요.

  4. 'JNI' 디렉터리에 이름, 플래그, 연결된 라이브러리, 컴파일할 소스 파일을 비롯하여 네이티브 라이브러리를 설명하는 Android.mk 파일을 만듭니다.

  5. 원하는 경우 타겟 ABI, 도구 모음, 출시/디버그 모드, STL을 구성하는 Application.mk 파일을 만들 수 있습니다. 이들 중 지정하지 않는 경우 각각 다음 기본값이 사용됩니다.

    • ABI: 지원 중단되지 않은 모든 ABI
    • 모드: 출시
    • STL: 시스템
  6. 프로젝트의 jni 디렉터리에 네이티브 소스를 넣습니다.

  7. ndk-build를 사용해 네이티브(.so, .a) 라이브러리를 컴파일합니다.

  8. 실행 가능한 .dex 파일을 생성하는 자바 구성요소를 빌드합니다.

  9. 앱 실행에 필요한 .so, .dex 및 기타 파일을 비롯하여 모든 항목을 APK 파일에 패키징합니다.

네이티브 액티비티 및 애플리케이션

Android SDK에서는 도우미 클래스 NativeActivity를 제공하며, 이를 사용하여 완전한 네이티브 액티비티를 작성할 수 있습니다. NativeActivity에서 Android 프레임워크와 네이티브 코드 간의 통신을 처리하므로 이를 서브클래스로 만들거나 메서드를 호출할 필요가 없습니다. AndroidManifest.xml 파일에서 애플리케이션을 네이티브로 선언하고 네이티브 애플리케이션을 만들기 시작하면 됩니다.

NativeActivity를 사용하는 Android 애플리케이션은 다른 애플리케이션에서 샌드박스된 자체 가상 머신에서 계속 실행됩니다. 따라서 JNI를 통해 Android 프레임워크 API에 계속 액세스할 수 있습니다. 하지만 센서, 입력 이벤트, 애셋 등 일부 경우에는 NDK에서 JNI를 통해 호출하지 않고 사용할 수 있는 네이티브 인터페이스를 제공합니다. 이러한 지원에 관한 자세한 내용은 네이티브 API를 참고하세요.

네이티브 액티비티 개발 여부와 상관없이 기존 Android 빌드 도구로 프로젝트를 만드는 것이 좋습니다. 이는 올바른 구조의 Android 애플리케이션을 빌드하고 패키징하는 데 도움이 됩니다.

Android NDK를 사용하면 다음 두 가지 방법으로 네이티브 액티비티를 구현할 수 있습니다.

  • native_activity.h 헤더는 NativeActivity 클래스의 네이티브 버전을 정의합니다. 이 헤더에는 네이티브 액티비티를 만드는 데 필요한 콜백 인터페이스와 데이터 구조가 포함되어 있습니다. 애플리케이션의 기본 스레드에서 콜백을 처리하므로 콜백 구현이 차단되면 안 됩니다. 콜백 구현이 차단되면 콜백이 반환될 때까지 기본 스레드가 응답하지 않으므로 ANR(애플리케이션 응답 없음) 오류가 발생할 수 있습니다.
  • android_native_app_glue.h 파일은 native_activity.h 인터페이스를 기반으로 빌드된 정적 도우미 라이브러리를 정의합니다. 이를 통해 다른 스레드가 생성되며 이 스레드에서 콜백 또는 이벤트 루프의 입력 이벤트 등을 처리합니다. 이러한 이벤트를 별도의 스레드로 이동하면 콜백으로 인해 기본 스레드가 차단되는 것을 방지할 수 있습니다.

<ndk_root>/sources/android/native_app_glue/android_native_app_glue.c 소스도 제공되며 이를 통해 구현을 수정할 수 있습니다.

이 정적 라이브러리의 자세한 사용 방법은 네이티브 액티비티 샘플 애플리케이션과 관련 문서를 살펴보세요. <ndk_root>/sources/android/native_app_glue/android_native_app_glue.h 파일의 주석에도 추가 자료가 제공됩니다.

native_activity.h 인터페이스 사용

native_activity.h 인터페이스로 네이티브 액티비티를 구현하려면 다음 단계를 따르세요.

  1. 프로젝트의 루트 디렉터리에 jni/ 디렉터리를 만듭니다. 이 디렉터리는 모든 네이티브 코드를 저장합니다.

  2. AndroidManifest.xml 파일에 네이티브 액티비티를 선언합니다.

    애플리케이션에 자바 코드가 없으므로 android:hasCodefalse로 설정합니다.

    <application android:label="@string/app_name" android:hasCode="false">
    

    활동 태그의 android:name 속성을 NativeActivity로 설정해야 합니다.

    <activity android:name="android.app.NativeActivity"
              android:label="@string/app_name">
    

    meta-data 태그의 android:value 속성은 C/C++ main과 같이 애플리케이션의 진입점을 포함한 공유 라이브러리의 이름을 지정하며, 이때 라이브러리 이름에서 lib 접두사와 .so 접미사는 생략합니다.

    <manifest>
      <application>
        <activity>
          <meta-data android:name="android.app.lib_name"
                     android:value="native-activity" />
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
      </application>
    </manifest>
    
  3. 네이티브 액티비티용 파일을 생성한 다음 ANativeActivity_onCreate 변수에서 명명된 함수를 구현합니다. 네이티브 액티비티가 시작될 때 앱에서 이 함수를 호출합니다. C/C++의 main과 유사한 이 함수는 개발자가 작성해야 하는 다양한 콜백 구현의 함수 포인터를 포함한 ANativeActivity 구조체 포인터를 받습니다. ANativeActivity->callbacks의 관련 콜백 함수 포인터를 콜백 구현으로 설정합니다.

  4. ANativeActivity->instance 필드를 사용하려는 특정 데이터의 인스턴스 주소로 설정합니다.

  5. 시작할 때 활동에서 실행할 다른 작업이 있으면 구현합니다.

  6. ANativeActivity->callbacks에 설정한 콜백의 나머지 부분을 구현합니다. 콜백이 호출되는 시점에 관한 자세한 내용은 활동 수명 주기 관리를 참고하세요.

  7. 애플리케이션의 나머지 부분을 개발합니다.

  8. 빌드 시스템의 네이티브 모듈을 설명하는 Android.mk file을 프로젝트의 jni/ 디렉터리에 생성합니다. 자세한 내용은 Android.mk를 참고하세요.

  9. Android.mk 파일이 있다면 ndk-build 명령어를 사용하여 네이티브 코드를 컴파일합니다.

    cd <path>/<to>/<project>
    $NDK/ndk-build
    
  10. 평소와 같이 Android 프로젝트를 빌드하고 설치합니다. 네이티브 코드가 jni/ 디렉터리에 있으면 빌드 스크립트는 이 디렉터리에서 빌드된 .so 파일을 APK에 자동으로 패키징합니다.

추가 샘플 코드

NDK 샘플을 다운로드하려면 NDK 샘플을 참고하세요.