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 em 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 da versão usará na compilaçã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 as regras a seguir:

  • 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 da versão compile 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);
        }
    }