샘플: native-activity

native-activity 샘플은 NDK 설치 루트 아래 samples/native-activity에 있습니다. 자바 소스 코드가 포함되지 않은 순수 네이티브 애플리케이션에 대한 아주 간단한 예입니다. 자바 소스가 없어도 자바 컴파일러는 가상 머신이 실행할 실행 가능한 스텁을 만듭니다. 이 스텁은 실제 네이티브 프로그램의 래퍼 역할을 하며 .so 파일에 있습니다.

앱 자체는 단순히 전체 화면에 하나의 색상을 렌더링하고 탐지한 움직임에 반응하여 부분적으로 색상을 변경합니다.

AndroidManifest.xml

네이티브 코드만 있는 앱은 NativeActivity 프레임워크 클래스를 도입한 버전인 Android API 레벨 9 이전 버전은 지정하지 않아야 합니다.

    <uses-sdk android:minSdkVersion="9" />
    

이 앱에는 자바 없이 네이티브 코드만 있으므로 다음 줄은 android:hasCodefalse로 선언합니다.

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

다음 줄은 NativeActivity 클래스를 선언합니다.

    <activity android:name="android.app.NativeActivity"
    

마지막으로 manifest는 빌드될 공유 라이브러리의 이름으로 android:value를 지정하되, 맨 처음의 lib.so 확장자는 뺍니다. 이 값은 Android.mkLOCAL_MODULE 이름과 동일해야 합니다.

    <meta-data android:name="android.app.lib_name"
            android:value="native-activity" />
    

Android.mk

이 파일은 생성할 공유 라이브러리 이름으로 시작합니다.

    LOCAL_MODULE    := native-activity
    

다음에는 네이티브 소스 코드 파일 이름을 선언합니다.

    LOCAL_SRC_FILES := main.c
    

그 다음에는 바이너리 빌드에 사용할 빌드 시스템의 외부 라이브러리 목록을 표시합니다. -l(link-against) 옵션은 각 라이브러리 이름 앞에 옵니다.

  • log는 로그 기록 라이브러리입니다.
  • android에는 NDK용 표준 Android 지원 API가 포함됩니다. Android 및 NDK에서 지원하는 API에 대한 자세한 내용은 Android NDK 네이티브 API를 참조하세요.
  • EGL은 그래픽 API의 플랫폼별 부분에 해당합니다.
  • GLESv1_CM은 Android용 OpenGL 버전인 OpenGL ES에 해당합니다. 이 라이브러리는 EGL에 따라 달라집니다.

각 라이브러리의 경우:

  • 실제 파일 이름은 lib로 시작하고 .so 확장자로 끝납니다. 예를 들어 log 라이브러리의 실제 파일 이름은 liblog.so입니다.
  • 라이브러리는 NDK 루트인 <ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/ 디렉토리에 있습니다.
    LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM
    

다음 줄은 정적 라이브러리 이름인 android_native_app_glue를 제공합니다. 이 이름은 애플리케이션이 NativeActivity 수명 주기 이벤트와 터치 입력을 관리하는 데 사용합니다.

    LOCAL_STATIC_LIBRARIES := android_native_app_glue
    

마지막 줄은 빌드 시스템에게 이 정적 라이브러리를 빌드하라고 지시합니다. ndk-build 스크립트는 빌드 프로세스 중 생성된 obj 디렉토리에 빌드된 라이브러리(libandroid_native_app_glue.a)를 배치합니다. android_native_app_glue 라이브러리에 대한 자세한 내용은 android_native_app_glue.h 헤더 및 해당 .c 소스 파일을 참조하세요.

    $(call import-module,android/native_app_glue)
    

Android.mk 파일에 대한 자세한 내용은 Android.mk를 참조하세요.

main.c

기본적으로 이 파일에는 프로그램 전체가 포함됩니다.

다음 include는 Android.mk에 열거된 공유 라이브러리 및 정적 라이브러리 모두에 해당합니다.

    #include <EGL/egl.h>
    #include <GLES/gl.h>

    #include <android/sensor.h>
    #include <android/log.h>
    #include <android_native_app_glue>
    

android_native_app_glue 라이브러리는 다음 함수를 호출하여 사전 정의된 상태 구조체를 전달합니다. NativeActivity 콜백 처리를 간소화하는 래퍼 역할도 합니다.

    void android_main(struct android_app* state) {
    

다음으로 이 프로그램은 글루 라이브러리에서 대기하는 이벤트를 처리합니다. 이 이벤트 핸들러는 상태 구조체 뒤에 나옵니다.

    struct engine engine;

    // Suppress link-time optimization that removes unreferenced code
    // to make sure glue isn't stripped.
    app_dummy();

    memset(&engine, 0, sizeof(engine));
    state->userData = &engine;
    state->onAppCmd = engine_handle_cmd;
    state->onInputEvent = engine_handle_input;
    engine.app = state;
    

애플리케이션은 sensor.h의 API를 사용하여 센서 모니터링을 시작할 준비를 합니다.

        engine.sensorManager = ASensorManager_getInstance();
        engine.accelerometerSensor =
                        ASensorManager_getDefaultSensor(engine.sensorManager,
                            ASENSOR_TYPE_ACCELEROMETER);
        engine.sensorEventQueue =
                        ASensorManager_createEventQueue(engine.sensorManager,
                            state->looper, LOOPER_ID_USER, NULL, NULL);
    

이어서 루프가 시작됩니다. 여기서 애플리케이션이 시스템에 메시지(센서 이벤트)를 폴링합니다. android_native_app_glue로 메시지를 전송하면 android_main에 정의된 onAppCmd 이벤트와 일치하는지 여부를 확인합니다. 일치하면 실행할 핸들러로 메시지가 전송됩니다.

    while (1) {
            // Read all pending events.
            int ident;
            int events;
            struct android_poll_source* source;

            // If not animating, we will block forever waiting for events.
            // If animating, we loop until all events are read, then continue
            // to draw the next frame of animation.
            while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL,
                    &events,
                    (void**)&source)) >= 0) {

                // Process this event.
                if (source != NULL) {
                    source->process(state, source);
                }

                // If a sensor has data, process it now.
                if (ident == LOOPER_ID_USER) {
                    if (engine.accelerometerSensor != NULL) {
                        ASensorEvent event;
                        while (ASensorEventQueue_getEvents(engine.sensorEventQueue,
                                &event, 1) > 0) {
                            LOGI("accelerometer: x=%f y=%f z=%f",
                                    event.acceleration.x, event.acceleration.y,
                                    event.acceleration.z);
                        }
                    }
                }

            // Check if we are exiting.
            if (state->destroyRequested != 0) {
                engine_term_display(&engine);
                return;
            }
        }
    

큐가 비워지고 프로그램이 폴링 루프를 종료하면 프로그램은 OpenGL을 호출하여 화면을 그리도록 합니다.

        if (engine.animating) {
            // Done with events; draw next animation frame.
            engine.state.angle += .01f;
            if (engine.state.angle > 1) {
                engine.state.angle = 0;
            }

            // Drawing is throttled to the screen update rate, so there
            // is no need to do timing here.
            engine_draw_frame(&engine);
        }
    }