Amostra: native-activity

A amostra native-activity fica na raiz de instalação do NDK, em samples/native-activity. Essa é uma amostra bem simples de um app puramente nativo, sem código-fonte Java. Mesmo na ausência de fontes Java, o compilador dessa linguagem ainda cria um stub executável para que a máquina virtual execute. O stub funciona como um wrapper para o programa nativo, localizado no arquivo .so.

O app em si apenas renderiza uma cor em toda a tela e a muda a parcialmente em resposta ao movimento detectado.

AndroidManifest.xml

Um app apenas com código nativo não pode especificar um nível de API do Android anterior a 9, que introduziu a classe de framework NativeActivity.

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

A linha a seguir declara android:hasCode como false, uma vez que esse app tem apenas código nativo, e não utiliza Java.

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

A próxima linha declara a classe NativeActivity.

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

Por fim, o manifesto especifica android:value como o nome da biblioteca compartilhada a ser compilada, menos a lib inicial e a extensão .so. Esse valor precisa ser igual ao nome de LOCAL_MODULE no Android.mk.

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

Android.mk

Esse arquivo começa fornecendo o nome da biblioteca compartilhada a ser gerada.

LOCAL_MODULE    := native-activity

Em seguida, ele declara o nome do arquivo de código-fonte nativo.

LOCAL_SRC_FILES := main.c

Então, ele lista as bibliotecas externas que o sistema de compilação usará na criação do binário. A opção -l (vincular) precede o nome de cada biblioteca.

  • log é uma biblioteca de registro.
  • android envolve as APIs de compatibilidade padrão do Android para NDK. Para saber mais sobre as APIs compatíveis com o Android e o NDK, consulte APIs nativas do Android NDK.
  • EGL corresponde à parte específica da plataforma da API de gráficos.
  • GLESv1_CM corresponde ao OpenGL ES, a versão do OpenGL para Android. Essa biblioteca depende de EGL.

Todas as bibliotecas seguem estas regras:

  • O nome real do arquivo começa com lib e termina com a extensão .so. Por exemplo, o nome real do arquivo da biblioteca log é liblog.so.
  • A biblioteca fica localizada no seguinte diretório, na raiz do NDK: <ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/.
LOCAL_LDLIBS    := -llog -landroid -lEGL -lGLESv1_CM

A próxima linha fornece o nome da biblioteca estática, android_native_app_glue, usada pelo app para gerenciar a entrada de toque e os eventos de ciclo de vida de NativeActivity.

LOCAL_STATIC_LIBRARIES := android_native_app_glue

A linha final solicita que o sistema de compilação crie essa biblioteca estática. O script ndk-build coloca a biblioteca compilada (libandroid_native_app_glue.a) no diretório obj gerado durante o processo de compilação. Para saber mais sobre a biblioteca android_native_app_glue, consulte o cabeçalho android_native_app_glue.h e o arquivo de origem .c correspondente.

$(call import-module,android/native_app_glue)

Para saber mais sobre o arquivo Android.mk, consulte Android.mk.

main.c

Esse arquivo contém todo o programa.

Os includes a seguir correspondem às bibliotecas, compartilhadas e estáticas, listadas em Android.mk.

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

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

A biblioteca android_native_app_glue chama a função a seguir, passando a ela uma estrutura de estado predefinida. Além disso, ela funciona como um wrapper que simplifica o gerenciamento de callbacks de NativeActivity.

void android_main(struct android_app* state) {

Em seguida, o programa gerencia os eventos enfileirados pela biblioteca agrupadora. O manipulador de eventos segue a estrutura do 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;

O aplicativo se prepara para iniciar o monitoramento dos sensores, usando as APIs em 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);

Em seguida, é iniciado um loop, em que o app consulta o sistema em busca de mensagens (eventos do sensor). Ele envia mensagens para android_native_app_glue, que verifica se elas correspondem a algum evento onAppCmd definido em android_main. Quando ocorre uma correspondência, a mensagem é enviada ao gerenciador para execução.

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

Quando a fila está vazia e o programa sai do loop de consulta, o programa chama o OpenGL para desenhar a tela.

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