Exemple : native-activity

L'exemple native-activity se trouve sous la racine d'exemples du NDK, dans le dossier native-activity. Il s'agit d'un exemple très simple d'application 100 % native, sans code source Java. En l'absence de source Java, le compilateur Java crée toujours un bouchon exécutable pour l'exécution de la machine virtuelle. Ce bouchon sert de wrapper au programme natif réel, situé dans le fichier .so.

L'application elle-même affiche simplement une couleur sur l'ensemble de l'écran, puis la modifie partiellement en réponse au mouvement qu'elle détecte.

AndroidManifest.xml

Une application qui ne comporte que du code natif ne doit pas spécifier un niveau d'API Android inférieur à 9, niveau à partir duquel la classe de framework NativeActivity a été ajoutée.

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

La ligne suivante déclare android:hasCode comme false, car cette application ne contient que du code natif, pas de code Java.

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

La ligne suivante déclare la classe NativeActivity.

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

Enfin, le fichier manifeste spécifie android:value comme nom de la bibliothèque partagée à compiler, moins la mention initiale lib et l'extension .so. Cette valeur doit être identique au nom de LOCAL_MODULE dans Android.mk.

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

Android.mk

Ce fichier commence par indiquer le nom de la bibliothèque partagée à générer.

LOCAL_MODULE    := native-activity

Ensuite, il déclare le nom du fichier de code source natif.

LOCAL_SRC_FILES := main.c

Puis, il indique les bibliothèques externes que le système de compilation doit utiliser pour compiler le binaire. L'option -l (link-against) précède chaque nom de bibliothèque.

  • log est une bibliothèque de journalisation.
  • android inclut les API standard de prise en charge Android pour le NDK. Pour en savoir plus sur les API prises en charge par Android et le NDK, consultez la page API natives du NDK Android.
  • EGL correspond à la partie de l'API de graphiques spécifique à la plate-forme.
  • GLESv1_CM correspond à OpenGL ES, la version d'OpenGL pour Android. Cette bibliothèque dépend d'EGL.

Pour chaque bibliothèque :

  • Le nom de fichier réel commence par lib et se termine par l'extension .so. Par exemple, le nom de fichier réel de la bibliothèque log est liblog.so.
  • La bibliothèque se trouve dans le répertoire suivant, sous la racine du NDK : <ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/.
LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM

La ligne suivante fournit le nom de la bibliothèque statique, android_native_app_glue, que l'application utilise pour gérer les événements de cycle de vie NativeActivity et la saisie tactile.

LOCAL_STATIC_LIBRARIES := android_native_app_glue

La dernière ligne indique au système de compilation de compiler cette bibliothèque statique. Le script ndk-build place la bibliothèque compilée (libandroid_native_app_glue.a) dans le répertoire obj généré lors du processus de compilation. Pour en savoir plus sur la bibliothèque android_native_app_glue, consultez son en-tête android_native_app_glue.h et le fichier source .c correspondant.

$(call import-module,android/native_app_glue)

Pour en savoir plus sur le fichier Android.mk, consultez Android.mk.

main.c

Ce fichier contient principalement la totalité du programme.

Les éléments suivants correspondent aux bibliothèques, partagées et statiques, énumérées dans Android.mk.

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


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

La bibliothèque android_native_app_glue appelle la fonction suivante, en lui transmettant une structure d'état prédéfinie. Elle sert également de wrapper pour simplifier le traitement des rappels NativeActivity.

void android_main(struct android_app* state) {

Ensuite, le programme gère les événements mis en file d'attente par la bibliothèque Glue. Le gestionnaire d'événements suit la structure des états.

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;

L'application se prépare à démarrer la surveillance des capteurs à l'aide des API de 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);

Une boucle commence ensuite, au cours de laquelle l'application interroge le système à la recherche de messages (événements de capteurs). Elle envoie des messages à android_native_app_glue, qui vérifie s'ils correspondent à des événements onAppCmd définis dans android_main. En cas de correspondance, le message est envoyé au handler pour exécution.

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

Une fois que la file d'attente est vide et que le programme quitte la boucle d'interrogation, le programme appelle OpenGL pour dessiner l'écran.

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