Esempio: teiera

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 file TeapotNativeActivity 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;