Przykład: aktywność natywna

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 biblioteki log to liblog.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 .cpliku ź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);
    }
}