Ejemplo: native-activity

El ejemplo native-activity se encuentra en la raíz de la instalación del NDK, en samples/native-activity. Es un ejemplo muy simple de una aplicación puramente nativa sin código fuente Java. Ante la ausencia de código Java, el compilador Java crea de todos modos un código auxiliar ejecutable para que se ejecute la máquina virtual. El código auxiliar sirve como contenedor del programa nativo, que se encuentra en el archivo .so.

La app simplemente muestra 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 Android API inferior al 9, que es el que introdujo la clase de marco de trabajo NativeActivity.

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

La línea que sigue 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 la 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
    

A continuación, enumera las bibliotecas externas que usará el sistema de compilación durante la compilación del ejecutable. La opción -l (vinculación) antecede al nombre de cada biblioteca.

  • log es una biblioteca de registro.
  • android abarca las API admitidas por Android para el NDK. Para obtener más información acerca de las API admitidas por Android y el NDK, consulta Android NDK Native API.
  • 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 lib y termina con la extensión .so. Por ejemplo, el nombre real del archivo para la biblioteca log es liblog.so.
  • La biblioteca se encuentra 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 línea que sigue 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 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 su archivo de origen .c correspondiente.

    $(call import-module,android/native_app_glue)
    

Para obtener más información sobre el archivo Android.mk Android.mk.

main.c

Este archivo básicamente contiene 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) {
    

A continuación, el programa maneja eventos que la biblioteca glue dispuso 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 controlar los sensores usando 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 de 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, el programa llama a OpenGL para diseñar 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);
        }
    }