native-activity 샘플은 NDK 샘플 루트 아래의 native-activity
폴더에 있습니다. 자바 소스 코드가 포함되지 않은 순수 네이티브 애플리케이션의 아주 간단한 예입니다. 자바 소스가 없어도 자바 컴파일러는 가상 머신이 실행할 실행 가능 스터브를 만듭니다.
이 스텁은 실제 네이티브 프로그램의 래퍼 역할을 하며 .so
파일에 있습니다.
앱 자체는 단순히 전체 화면에 하나의 색상을 렌더링하고 감지된 움직임에 반응하여 부분적으로 색상을 변경합니다.
AndroidManifest.xml
네이티브 코드만 있는 앱은 NativeActivity
프레임워크 클래스를 도입한 Android API 수준 9 이전 버전을 지정해서는 안 됩니다.
<uses-sdk android:minSdkVersion="9" />
이 앱에는 자바 없이 네이티브 코드만 있으므로 다음 행은 android:hasCode
를 false
로 선언합니다.
<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); } }