A amostra native-activity fica na
raiz de amostras do NDK, na pasta
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 build vai 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 bibliotecalog
é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 por 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); } }