Ejemplo: native-activity

La muestra native-activity se encuentra en la raíz de las muestras del NDK, en la carpeta native-activity. Es un ejemplo muy simple de una aplicación puramente nativa sin código fuente Java. Ante la ausencia de este código, el compilador de Java crea igualmente un stub ejecutable para que se ejecute la máquina virtual. El stub sirve como wrapper del programa nativo real, que se encuentra en el archivo .so.

La app simplemente renderiza un color en toda la pantalla y luego cambia parcialmente el color en respuesta al movimiento que detecta.

AndroidManifest.xml

Una app que solo contiene código nativo no debe especificar un nivel de API de Android inferior al 9, que es el que introdujo la clase de marco de trabajo NativeActivity.

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

La siguiente línea declara android:hasCode como false, ya que esta app solo tiene código nativo, no Java.

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

La línea que sigue declara la clase NativeActivity.

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

Por último, el manifiesto especifica android:value como nombre de la biblioteca compartida que se compilará, menos el elemento lib inicial y la extensión .so. Este valor debe ser el mismo que el nombre de LOCAL_MODULE en Android.mk.

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

Android.mk

Este archivo comienza proporcionando el nombre de la biblioteca compartida que se generará.

LOCAL_MODULE    := native-activity

Luego, declara el nombre del archivo de código fuente nativo.

LOCAL_SRC_FILES := main.c

Luego, enumera las bibliotecas externas que usará el sistema de compilación para crear el objeto binario. La opción -l (vinculación) antecede al nombre de cada biblioteca.

  • log es una biblioteca de registro.
  • android abarca las API que admite Android para el NDK. Si quieres obtener más información sobre las API compatibles con Android y el NDK, consulta la página de las API nativas del NDK de Android.
  • EGL corresponde a la parte de la API de gráficos específica de la plataforma.
  • GLESv1_CM corresponde a OpenGL ES, la versión de OpenGL para Android. Esta biblioteca se basa en EGL.

Para cada biblioteca:

  • El nombre real del archivo comienza con el elemento lib y termina con la extensión .so. Por ejemplo, el nombre real del archivo para la biblioteca log es liblog.so.
  • La biblioteca reside en el siguiente directorio raíz del NDK: <ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/.
LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM

La siguiente línea proporciona el nombre de la biblioteca estática, android_native_app_glue, que la aplicación usa para administrar eventos de ciclo de vida de NativeActivity y entrada táctil.

LOCAL_STATIC_LIBRARIES := android_native_app_glue

La línea final le indica al sistema de compilación que compile esta biblioteca estática. La secuencia de comandos ndk-build ubica la biblioteca compilada (libandroid_native_app_glue.a) en el directorio obj generado durante el proceso de compilación. Para obtener más información sobre la biblioteca android_native_app_glue, consulta su encabezado android_native_app_glue.h y el archivo de origen .c correspondiente.

$(call import-module,android/native_app_glue)

Para más información sobre el archivo Android.mk, consulta la página de Android.mk.

main.c

Este archivo contiene básicamente todo el programa.

Las siguientes inclusiones corresponden a las bibliotecas, tanto compartidas como estáticas, que se indican en Android.mk.

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


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

La biblioteca android_native_app_glue llama a la siguiente función y le pasa una estructura de estado predefinida. También sirve como un wrapper que simplifica el manejo de devoluciones de llamada de NativeActivity.

void android_main(struct android_app* state) {

Luego, el programa maneja eventos que la biblioteca glue pone en cola. El controlador de eventos sigue la estructura de estado.

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;

La aplicación se prepara para comenzar a supervisar los sensores mediante las 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);

A continuación, se inicia un bucle, en el cual la aplicación sondea el sistema para detectar mensajes (eventos del sensor). Le envía mensajes a android_native_app_glue, que comprueba si coinciden con alguno de los eventos onAppCmd definidos en android_main. Cuando se encuentra una coincidencia, se envía el mensaje al controlador para su ejecución.

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

Una vez que la cola está vacía y el programa sale del bucle de sondeo, este llama a OpenGL para traer la pantalla.

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