L'esempio Teapot si trova nella directory samples/Teapot/
, sotto NDK
nella directory radice dell'installazione. Questo esempio utilizza la libreria OpenGL per eseguire il rendering dell'iconico
Utah
di cottura. In particolare, mostra il corso per aiutanti ndk_helper
,
una raccolta di funzioni helper native necessarie per l'implementazione di giochi e
simili a quelle native. Questo corso fornisce:
- Un livello di astrazione,
GLContext
, che gestisce alcuni comportamenti specifici dei NDK. - Funzioni helper utili ma non presenti nell'NDK, ad esempio il rilevamento del tocco.
- I wrapper per JNI richiamano le funzionalità della piattaforma come il caricamento delle texture.
AndroidManifest.xml
La dichiarazione delle attività qui non è NativeActivity
stessa, ma
una sottoclasse: TeapotNativeActivity
.
<activity android:name="com.sample.teapot.TeapotNativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
Essenzialmente, il nome del file dell'oggetto condiviso creato dal sistema di compilazione
libTeapotNativeActivity.so
. Il sistema di compilazione aggiunge il prefisso lib
e .so
estensione; nessuno dei due fa parte del valore assegnato originariamente dal file manifest
android:value
.
<meta-data android:name="android.app.lib_name" android:value="TeapotNativeActivity" />
Application.mk
Un'app che utilizza la classe framework NativeActivity
non deve specificare un
Livello API Android inferiore a 9, che ha introdotto questa classe. Per ulteriori informazioni
NativeActivity
corso, vedi
Attività e applicazioni native.
APP_PLATFORM := android-9
La riga successiva indica al sistema di compilazione di creare per tutte le architetture supportate.
APP_ABI := all
Quindi, il file indica al sistema di compilazione libreria di supporto del runtime C++ da utilizzare.
APP_STL := stlport_static
Implementazione lato Java
Il fileTeapotNativeActivity
si trova nel percorso teapots/classic-teapot/src/com/sample/teapot
, nella directory root del repository NDK su GitHub. Gestisce gli eventi del ciclo di vita dell'attività, crea una finestra popup per visualizzare il testo sullo schermo con la funzione ShowUI()
e aggiorna la frequenza fotogrammi in modo dinamico con la funzione updateFPS()
. Potrebbe interessarti il seguente codice in quanto prepara l'attività dell'app a schermo intero, immersiva e senza barre di navigazione di sistema, in modo che l'intero schermo possa essere utilizzato per visualizzare i frame delle teapot renderizzati:
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); }
Implementazione lato nativo
Questa sezione esplora la parte dell'app Teapot implementata in C++.
TeapotRenderer.h
Queste chiamate di funzione eseguono il rendering effettivo della teapot. Utilizza
ndk_helper
per il calcolo della matrice e per riposizionare la fotocamera
in base a dove l'utente tocca.
ndk_helper::Mat4 mat_projection_; ndk_helper::Mat4 mat_view_; ndk_helper::Mat4 mat_model_; ndk_helper::TapCamera* camera_;
TeapotNativeActivity.cpp
Le seguenti righe includono ndk_helper
nel file di origine nativo e definiscono la
nome della classe helper.
#include "NDKHelper.h" //------------------------------------------------------------------------- //Preprocessor //------------------------------------------------------------------------- #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
Il primo utilizzo della classe ndk_helper
è per gestire
Ciclo di vita correlato a EGL, associando gli stati di contesto EGL (creato/perso) a
Eventi del ciclo di vita di Android. La classe ndk_helper
consente all'applicazione di preservare il contesto
informazioni in modo che il sistema possa ripristinare un'attività eliminata. Questa funzionalità è utile per
Ad esempio, quando la macchina target viene
ruotata, generando un'attività
distrutta e poi immediatamente ripristinata nel nuovo orientamento), oppure quando il blocco
viene visualizzata la schermata iniziale.
ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.
In seguito, ndk_helper
offre il controllo touch.
ndk_helper::DoubletapDetector doubletap_detector_; ndk_helper::PinchDetector pinch_detector_; ndk_helper::DragDetector drag_detector_; ndk_helper::PerfMonitor monitor_;
Offre inoltre il controllo della videocamera (tronco di visualizzazione openGL).
ndk_helper::TapCamera tap_camera_;
L'app si prepara quindi a usare i sensori del dispositivo, usando le API native fornite nell'NDK.
ASensorManager* sensor_manager_; const ASensor* accelerometer_sensor_; ASensorEventQueue* sensor_event_queue_;
L'app chiama le seguenti funzioni in risposta a varie
eventi del ciclo di vita e modifiche dello stato del contesto EGL, utilizzando varie funzionalità
fornita da ndk_helper
tramite il corso Engine
.
void LoadResources(); void UnloadResources(); void DrawFrame(); void TermDisplay(); void TrimMemory(); bool IsReady();
Quindi, la seguente funzione richiama il lato Java per aggiornare la visualizzazione dell'interfaccia utente.
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; }
Successivamente, questa funzione richiama il lato Java per disegnare una casella di testo sovrapposti a uno schermo sottoposto a rendering sul lato nativo, con cornice visibile conteggio.
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; }
L'applicazione ottiene l'orologio di sistema e lo fornisce al renderer per l'animazione temporale basata sull'orologio in tempo reale. Queste informazioni vengono utilizzate, ad esempio, nella calcolando la quantità di moto, in cui la velocità diminuisce in funzione del tempo.
renderer_.Update( monitor_.GetCurrentTime() );
L'applicazione ora capovolge il frame sottoposto a rendering nel buffer anteriore per la visualizzazione tramite la funzione GLcontext::Swap()
. gestisce anche i possibili errori che si sono verificati durante il processo di flipping.
if( EGL_SUCCESS != gl_context_->Swap() ) // swaps buffer.
Il programma trasmette gli eventi di movimento touch al rilevatore di gesti definito
nel corso ndk_helper
. Il rilevatore di gesti traccia il multi-touch
gesti quali pizzica e trascina, e invia una notifica quando vengono attivati
uno qualsiasi di questi eventi.
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; }
La classe ndk_helper
dà anche accesso a una libreria di matematica vettoriale
(vecmath.h
), utilizzandolo qui per trasformare le coordinate di tocco.
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 ); }
Il metodo HandleCmd()
gestisce i comandi pubblicati
libreria android_native_app_glue. Per ulteriori informazioni su ciò che i messaggi
media, fai riferimento ai commenti in android_native_app_glue.h
e
.c
file sorgente.
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; } }
Il corso ndk_helper
pubblica APP_CMD_INIT_WINDOW
quando android_app_glue
riceve un callback onNativeWindowCreated()
dal sistema.
Le applicazioni possono normalmente eseguire l'inizializzazione delle finestre, ad esempio EGL
durante l'inizializzazione. Lo fanno al di fuori del ciclo di vita dell'attività, poiché
l'attività non è ancora 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;