A amostra Teapot está localizada no diretório samples/Teapot/
, no diretório raiz da
instalação do NDK. Essa amostra usa a biblioteca OpenGL para renderizar o icônico Utah teapot (link em inglês). Mais especificamente, ela demonstra o uso da classe auxiliar ndk_helper
, uma coleção de funções auxiliares nativas necessárias para a implementação de jogos e apps parecidos, como aplicativos nativos. Essa classe fornece:
- uma camada de abstração,
GLContext
, que gerencia certos comportamentos específicos do NDK; - funções auxiliares que são úteis, mas ausentes no NDK, como detecção de toque;
- wrappers para chamadas de JNI para recursos de plataforma, como carregamento de textura.
AndroidManifest.xml
A declaração de atividade apresentada aqui não é uma NativeActivity
, mas uma subclasse dela: TeapotNativeActivity
.
<activity android:name="com.sample.teapot.TeapotNativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
O nome do arquivo do objeto compartilhado que o sistema de compilação cria é libTeapotNativeActivity.so
. O sistema de compilação acrescenta o prefixo lib
e a extensão .so
. Nenhum desses dois faz parte do valor atribuído originalmente pelo manifesto ao android:value
.
<meta-data android:name="android.app.lib_name" android:value="TeapotNativeActivity" />
Application.mk
Apps que usam a classe de framework NativeActivity
não podem especificar uma
API do Android de nível anterior a 9, que introduziu essa classe. Para saber mais sobre a classe NativeActivity
, consulte Apps e atividades nativos.
APP_PLATFORM := android-9
A próxima linha instrui o sistema de build a criar para todas as arquiteturas com suporte.
APP_ABI := all
Em seguida, o arquivo instrui qual Biblioteca de Suporte de ambiente de execução C++ o sistema precisa usar.
APP_STL := stlport_static
Implementação em Java
O arquivoTeapotNativeActivity
está localizado em teapots/classic-teapot/src/com/sample/teapot
, no diretório raiz do repositório NDK no GitHub. Ele gerencia eventos de ciclo de vida da atividade, cria uma janela pop-up para exibir texto na tela com a função ShowUI()
e atualiza dinamicamente o frame rate com a função updateFPS()
. O código a seguir pode ser interessante para você, porque prepara a atividade do app para ser exibida em tela cheia, de forma imersiva e sem barras de navegação do sistema, de modo que toda a tela possa ser usada para exibir frames teapot renderizados:
Kotlin
fun setImmersiveSticky() { window.decorView.systemUiVisibility = ( View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE ) }
Java
void setImmersiveSticky() { View decorView = getWindow().getDecorView(); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); }
Implementação nativa
Esta seção explora a parte do app Teapot implementado em C++.
TeapotRenderer.h
Essas chamadas de função executam a renderização real do teapot. Elas usam ndk_helper
para cálculo de matriz e para reposicionar a câmera com base no local onde o usuário toca.
ndk_helper::Mat4 mat_projection_; ndk_helper::Mat4 mat_view_; ndk_helper::Mat4 mat_model_; ndk_helper::TapCamera* camera_;
TeapotNativeActivity.cpp
As linhas a seguir incluem ndk_helper
no arquivo de origem nativo e definem o nome da classe auxiliar.
#include "NDKHelper.h" //------------------------------------------------------------------------- //Preprocessor //------------------------------------------------------------------------- #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
O primeiro uso da classe ndk_helper
é para gerenciar o ciclo de vida relacionado a EGL, associando os estados de contexto de EGL (criados/perdidos) com eventos de ciclo de vida do Android. A classe ndk_helper
permite que o app preserve as informações de contexto de modo que o sistema possa restaurar uma atividade destruída. Essa habilidade é útil, por exemplo, quando a máquina de destino é girada, causando a destruição de uma atividade e a restauração imediata na nova orientação, ou quando a tela de bloqueio aparece.
ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.
A seguir, o ndk_helper
fornece controle por toque.
ndk_helper::DoubletapDetector doubletap_detector_; ndk_helper::PinchDetector pinch_detector_; ndk_helper::DragDetector drag_detector_; ndk_helper::PerfMonitor monitor_;
Também fornece controle de câmera (tronco de exibição do openGL).
ndk_helper::TapCamera tap_camera_;
Então, o app se prepara para usar os sensores do dispositivo, usando as APIs nativas fornecidas no NDK.
ASensorManager* sensor_manager_; const ASensor* accelerometer_sensor_; ASensorEventQueue* sensor_event_queue_;
O app chama as seguintes funções em resposta a vários eventos do ciclo de vida do Android e mudanças no estado do contexto de EGL, usando diversas funcionalidades fornecidas por ndk_helper
pela classe Engine
.
void LoadResources(); void UnloadResources(); void DrawFrame(); void TermDisplay(); void TrimMemory(); bool IsReady();
Depois, a função a seguir chama o lado do Java para atualizar a exibição da IU.
void Engine::ShowUI() { JNIEnv *jni; app_->activity->vm->AttachCurrentThread( &jni, NULL ); //Default class retrieval jclass clazz = jni->GetObjectClass( app_->activity->clazz ); jmethodID methodID = jni->GetMethodID( clazz, "showUI", "()V" ); jni->CallVoidMethod( app_->activity->clazz, methodID ); app_->activity->vm->DetachCurrentThread(); return; }
Então, essa função chama o lado do Java para desenhar uma caixa de texto sobreposta à tela e renderizada no lado nativo, mostrando a contagem de frames.
void Engine::UpdateFPS( float fFPS ) { JNIEnv *jni; app_->activity->vm->AttachCurrentThread( &jni, NULL ); //Default class retrieval jclass clazz = jni->GetObjectClass( app_->activity->clazz ); jmethodID methodID = jni->GetMethodID( clazz, "updateFPS", "(F)V" ); jni->CallVoidMethod( app_->activity->clazz, methodID, fFPS ); app_->activity->vm->DetachCurrentThread(); return; }
O app recebe o relógio do sistema e o fornece ao renderizador para animação do horário com base no relógio em tempo real. Essas informações são usadas, por exemplo, no momento do cálculo, quando a velocidade diminui em função do tempo.
renderer_.Update( monitor_.GetCurrentTime() );
O aplicativo agora inverte o frame renderizado para o buffer frontal para exibição por meio da função GLcontext::Swap()
. Ele também gerencia possíveis erros que ocorreram durante o processo de inversão.
if( EGL_SUCCESS != gl_context_->Swap() ) // swaps buffer.
O programa transmite eventos de toque/movimento ao detector de gestos definido na classe ndk_helper
. O detector de gestos rastreia gestos multitoque, como fazer um gesto de pinça e arrastar, e envia uma notificação quando é acionado por qualquer um desses eventos.
if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION ) { ndk_helper::GESTURE_STATE doubleTapState = eng->doubletap_detector_.Detect( event ); ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event ); ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event ); //Double tap detector has a priority over other detectors if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION ) { //Detect double tap eng->tap_camera_.Reset( true ); } else { //Handle drag state if( dragState & ndk_helper::GESTURE_STATE_START ) { //Otherwise, start dragging ndk_helper::Vec2 v; eng->drag_detector_.GetPointer( v ); eng->TransformPosition( v ); eng->tap_camera_.BeginDrag( v ); } // ...else other possible drag states... //Handle pinch state if( pinchState & ndk_helper::GESTURE_STATE_START ) { //Start new pinch ndk_helper::Vec2 v1; ndk_helper::Vec2 v2; eng->pinch_detector_.GetPointers( v1, v2 ); eng->TransformPosition( v1 ); eng->TransformPosition( v2 ); eng->tap_camera_.BeginPinch( v1, v2 ); } // ...else other possible pinch states... } return 1; }
A classe ndk_helper
também fornece acesso a uma biblioteca matemática de vetores (vecmath.h
), que é usada aqui para transformar coordenadas de toque.
void Engine::TransformPosition( ndk_helper::Vec2& vec ) { vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec / ndk_helper::Vec2( gl_context_->GetScreenWidth(), gl_context_->GetScreenHeight() ) - ndk_helper::Vec2( 1.f, 1.f ); }
O método HandleCmd()
gerencia comandos postados pela biblioteca android_native_app_glue. Para saber mais sobre o significado das mensagens, consulte os comentários nos arquivos de origem android_native_app_glue.h
e .c
.
void Engine::HandleCmd( struct android_app* app, int32_t cmd ) { Engine* eng = (Engine*) app->userData; switch( cmd ) { case APP_CMD_SAVE_STATE: break; case APP_CMD_INIT_WINDOW: // The window is being shown, get it ready. if( app->window != NULL ) { eng->InitDisplay(); eng->DrawFrame(); } break; case APP_CMD_TERM_WINDOW: // The window is being hidden or closed, clean it up. eng->TermDisplay(); eng->has_focus_ = false; break; case APP_CMD_STOP: break; case APP_CMD_GAINED_FOCUS: eng->ResumeSensors(); //Start animation eng->has_focus_ = true; break; case APP_CMD_LOST_FOCUS: eng->SuspendSensors(); // Also stop animating. eng->has_focus_ = false; eng->DrawFrame(); break; case APP_CMD_LOW_MEMORY: //Free up GL resources eng->TrimMemory(); break; } }
A classe ndk_helper
posta APP_CMD_INIT_WINDOW
quando android_app_glue
recebe um callback onNativeWindowCreated()
do sistema.
Os apps podem executar inicializações de janelas normalmente, como a inicialização de EGL. Eles fazem isso fora do ciclo de vida da atividade, uma vez que a atividade ainda não está pronta.
//Init helper functions ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME ); state->userData = &g_engine; state->onAppCmd = Engine::HandleCmd; state->onInputEvent = Engine::HandleInput;