Beispiel: native Aktivität

Das Beispiel für die native Aktivität befindet sich im NDK-Beispielstamm im Ordner native-activity. Es ist ein sehr einfaches Beispiel für eine rein native Anwendung ohne Java-Quellcode. Auch wenn keine Java-Quelle vorhanden ist, erstellt der Java-Compiler dennoch einen ausführbaren Stub, den die virtuelle Maschine ausführen kann. Der Stub dient als Wrapper für das eigentliche native Programm, das sich in der Datei .so befindet.

Die App selbst rendert einfach eine Farbe auf dem gesamten Bildschirm und ändert sie dann teilweise als Reaktion auf eine erkannte Bewegung.

AndroidManifest.xml

Eine App mit nur nativem Code darf kein Android API-Level unter 9 angeben. Dadurch wurde die Framework-Klasse NativeActivity eingeführt.

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

In der folgenden Zeile wird android:hasCode als false deklariert, da diese Anwendung nur nativen Code und kein Java hat.

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

In der nächsten Zeile wird die Klasse NativeActivity deklariert.

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

Schließlich gibt das Manifest android:value als Namen der gemeinsam zu erstellenden Bibliothek an, abzüglich der anfänglichen lib- und der .so-Erweiterung. Dieser Wert muss mit dem Namen von LOCAL_MODULE in Android.mk übereinstimmen.

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

Android.Mk

Zu Beginn dieser Datei wird der Name der gemeinsam genutzten Bibliothek angegeben, die generiert werden soll.

LOCAL_MODULE    := native-activity

Als Nächstes wird der Name der nativen Quellcodedatei deklariert.

LOCAL_SRC_FILES := main.c

Als Nächstes werden die externen Bibliotheken für das Build-System aufgelistet, die beim Erstellen der Binärdatei verwendet werden sollen. Jedem Bibliotheksnamen wird die Option -l (link-against) vorangestellt.

  • log ist eine Logging-Bibliothek.
  • android umfasst die standardmäßigen Android-Support-APIs für NDK. Weitere Informationen zu den APIs, die von Android und dem NDK unterstützt werden, findest du unter Android NDK Native APIs.
  • EGL entspricht dem plattformspezifischen Teil der Grafik-API.
  • GLESv1_CM entspricht OpenGL ES, der OpenGL-Version für Android. Diese Bibliothek hängt von EGL ab.

Führen Sie für jede Bibliothek folgende Schritte aus:

  • Der tatsächliche Dateiname beginnt mit lib und endet mit der Erweiterung .so. Der tatsächliche Dateiname für die Bibliothek log lautet beispielsweise liblog.so.
  • Die Bibliothek befindet sich im folgenden Verzeichnis, dem NDK-Stammverzeichnis: <ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/.
LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM

Die nächste Zeile enthält den Namen der statischen Bibliothek android_native_app_glue, mit der die Anwendung NativeActivity-Lebenszyklusereignisse und die Eingabe per Berührung verwaltet.

LOCAL_STATIC_LIBRARIES := android_native_app_glue

Die letzte Zeile weist das Build-System an, diese statische Bibliothek zu erstellen. Das Skript ndk-build platziert die erstellte Bibliothek (libandroid_native_app_glue.a) im Verzeichnis obj, das während des Build-Prozesses generiert wurde. Weitere Informationen zur android_native_app_glue-Bibliothek finden Sie im android_native_app_glue.h-Header und in der entsprechenden .c-Quelldatei.

$(call import-module,android/native_app_glue)

Weitere Informationen zur Datei Android.mk findest du unter Android.mk.

Main.c

Diese Datei enthält im Grunde das gesamte Programm.

Die folgenden Elemente entsprechen den freigegebenen und statischen Bibliotheken, die in Android.mk aufgezählt sind.

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


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

Die android_native_app_glue-Bibliothek ruft die folgende Funktion auf und übergibt ihr eine vordefinierte Statusstruktur. Es dient auch als Wrapper, der die Verarbeitung von NativeActivity-Callbacks vereinfacht.

void android_main(struct android_app* state) {

Als Nächstes verarbeitet das Programm Ereignisse, die von der Glue-Bibliothek in die Warteschlange gestellt wurden. Der Event-Handler folgt der Statusstruktur.

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;

Die Anwendung bereitet sich auf die Überwachung der Sensoren mithilfe der APIs in sensor.h vor.

    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);

Als Nächstes beginnt eine Schleife, in der die Anwendung das System nach Nachrichten (Sensorereignisse) abfragt. Nachrichten werden an android_native_app_glue gesendet, die prüft, ob sie mit den in android_main definierten onAppCmd-Ereignissen übereinstimmen. Bei einer Übereinstimmung wird die Nachricht zur Ausführung an den Handler gesendet.

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;
        }
    }

Sobald die Warteschlange leer ist und das Programm die Abfrageschleife verlässt, ruft das Programm OpenGL auf, um den Bildschirm zu zeichnen.

    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);
    }
}