샘플: native-activity

native-activity 샘플은 NDK 샘플 루트 아래의 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"

마지막으로 매니페스트는 빌드될 공유 라이브러리의 이름으로 android:value를 지정하되 맨 처음의 lib.so 확장자는 뺍니다. 이 값은 Android.mk에 있는 LOCAL_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 스크립트는 빌드된 라이브러리(libandroid_native_app_glue.a)를 빌드 프로세스 중 생성된 obj 디렉터리에 배치합니다. 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);
    }
}