Próbka aktywności natywnej znajduje się w katalogu głównym przykładów NDK w folderze native-activity
. To bardzo prosty przykład czysto natywnej aplikacji
bez kodu źródłowego w Javie. Jeśli nie ma żadnego źródła w języku Java, kompilator Java nadal tworzy plik wykonywalny na potrzeby maszyny wirtualnej.
Jest on obwódką samego programu natywnego, który znajduje się w pliku .so
.
Aplikacja po prostu renderuje kolor na całym ekranie, a potem częściowo zmienia go w odpowiedzi na wykryty ruch.
AndroidManifest.xml
Aplikacja zawierająca tylko kod natywny nie może określać poziomu interfejsu Android API niższego niż 9, co spowodowało wprowadzenie klasy platformy NativeActivity
.
<uses-sdk android:minSdkVersion="9" />
Ten wiersz określa android:hasCode
jako false
, ponieważ ta aplikacja zawiera tylko kod natywny, a nie Java.
<application android:label="@string/app_name" android:hasCode="false">
Następny wiersz deklaruje klasę NativeActivity
.
<activity android:name="android.app.NativeActivity"
W pliku manifestu jest podana nazwa android:value
biblioteki współdzielonej, która ma zostać utworzona, pomniejszona o lib
i .so
. Ta wartość musi być taka sama jak nazwa elementu LOCAL_MODULE
w pliku Android.mk
.
<meta-data android:name="android.app.lib_name" android:value="native-activity" />
Android.mk
Plik na początku zawiera nazwę biblioteki udostępnionej do wygenerowania.
LOCAL_MODULE := native-activity
W następnym kroku jest deklarowana nazwa natywnego pliku z kodem źródłowym.
LOCAL_SRC_FILES := main.c
Następnie zawiera listę bibliotek zewnętrznych, których system kompilacji będzie używać do skompilowania pliku binarnego. Przed każdą nazwą biblioteki znajduje się opcja -l
(z linkiem do adresu).
log
to biblioteka logowania.android
obejmuje standardowe interfejsy API do obsługi zestawu Android dla NDK. Więcej informacji o interfejsach API obsługiwanych przez Androida i pakiet NDK znajdziesz w artykule o natywnych interfejsach API na Androida NDK.EGL
odpowiada części interfejsu graficznego interfejsu API specyficznej dla danej platformy.GLESv1_CM
odpowiada OpenGL ES, czyli wersji OpenGL dla Androida. Ta biblioteka zależy od EGL.
Dla każdej biblioteki:
- Rzeczywista nazwa pliku zaczyna się od
lib
i kończy na.so
. Na przykład rzeczywista nazwa pliku bibliotekilog
toliblog.so
. - Biblioteka znajduje się w tym katalogu, głównym katalogu NDK:
<ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/
.
LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM
Następny wiersz zawiera nazwę biblioteki statycznej (android_native_app_glue
), której aplikacja używa do zarządzania zdarzeniami cyklu życia NativeActivity
i dotykowym wprowadzaniem danych.
LOCAL_STATIC_LIBRARIES := android_native_app_glue
Ostatni wiersz informuje system kompilacji, aby utworzyć tę bibliotekę statyczną.
Skrypt ndk-build
umieszcza wbudowaną bibliotekę (libandroid_native_app_glue.a
) w katalogu obj
wygenerowanym podczas procesu kompilacji. Więcej informacji o bibliotece android_native_app_glue
znajdziesz w jej nagłówku android_native_app_glue.h
i odpowiadającym mu .c
pliku źródłowym.
$(call import-module,android/native_app_glue)
Więcej informacji o pliku Android.mk
znajdziesz w Android.mk.
main.c
Ten plik zawiera zasadniczo cały program.
Poniższe przykłady odpowiadają bibliotekom, zarówno udostępnionym, jak i statycznym, wyszczególnionym w zasadzie Android.mk
.
#include <EGL/egl.h> #include <GLES/gl.h> #include <android/sensor.h> #include <android/log.h> #include <android_native_app_glue>
Biblioteka android_native_app_glue
wywołuje poniższą funkcję, przekazując jej wstępnie zdefiniowaną strukturę stanu. Służy też jako kod, który upraszcza obsługę wywołań zwrotnych NativeActivity
.
void android_main(struct android_app* state) {
Następnie program obsługuje wydarzenia dodane do kolejki w bibliotece klejów. Moduł obsługi zdarzeń zachowuje strukturę stanu.
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;
Aplikacja przygotowuje się do monitorowania czujników za pomocą interfejsów API w sensor.h
.
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);
Rozpoczyna się pętla, w której aplikacja sonduje system w poszukiwaniu wiadomości (zdarzeń z czujników). Wysyła wiadomości do usługi android_native_app_glue
, która sprawdza, czy pasują do zdarzeń onAppCmd
zdefiniowanych w zasadzie android_main
. Gdy wystąpi dopasowanie, wiadomość jest wysyłana do modułu obsługi w celu wykonania.
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; } }
Gdy kolejka jest pusta i program wychodzi z pętli odpytywania, wywołuje tryb OpenGL, aby wyświetlić ekran.
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); } }