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 bibliotecalog
esliblog.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); } }