Beispiel: Teekanne

Das Teapot-Beispiel befindet sich im Verzeichnis samples/Teapot/ unter dem NDK. das Stammverzeichnis der Installation. In diesem Beispiel wird die OpenGL-Bibliothek verwendet, Utah, USA Teekanne. Insbesondere wird die Hilfsklasse ndk_helper präsentiert, eine Sammlung nativer Hilfsfunktionen, die für die Implementierung von Spielen und ähnliche Anwendungen wie native Anwendungen. Dieser Kurs bietet folgende Vorteile:

  • Die Abstraktionsebene GLContext, die bestimmte NDK-spezifische Verhaltensweisen verarbeitet.
  • Hilfsfunktionen, die nützlich, aber im NDK nicht vorhanden sind, z. B. die Tipperkennung.
  • Wrapper für JNI-Aufrufe für Plattformfunktionen wie das Laden von Texturen.

AndroidManifest.xml

Die Erklärung zur Aktivität ist hier nicht „NativeActivity“, aber eine abgeleitete Klasse davon: TeapotNativeActivity.

<activity android:name="com.sample.teapot.TeapotNativeActivity"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden">

Letztendlich lautet der Name der gemeinsam genutzten Objektdatei, die das Build-System erstellt, libTeapotNativeActivity.so Das Build-System fügt das Präfix lib und den .so Erweiterung; Beide sind nicht Teil des Werts, dem das Manifest ursprünglich zuweist. android:value

<meta-data android:name="android.app.lib_name"
        android:value="TeapotNativeActivity" />

Anwendung.mk

In einer App, die die Framework-Klasse NativeActivity verwendet, darf kein Wert für Android API-Level niedriger als 9, in dem diese Klasse eingeführt wurde. Weitere Informationen zur NativeActivity Klasse, siehe Native Aktivitäten und Anwendungen.

APP_PLATFORM := android-9

In der nächsten Zeile wird das Build-System angewiesen, alle unterstützten Architekturen zu erstellen.

APP_ABI := all

Als Nächstes teilt die Datei dem Build-System mit, C++-Laufzeitsupportbibliothek, die verwendet werden soll.

APP_STL := stlport_static

Implementierung auf Java-Seite

Die Datei TeapotNativeActivity befindet sich in teapots/classic-teapot/src/com/sample/teapot im NDK-Repository-Stammverzeichnis auf GitHub. Sie verarbeitet Lebenszyklusereignisse von Aktivitäten, erstellt ein Pop-up-Fenster, um Text auf dem Bildschirm mit der Funktion „ShowUI()“ anzuzeigen, und aktualisiert die Framerate dynamisch mit der Funktion „updateFPS()“. Der folgende Code könnte interessant für Sie sein, da er die Activity-Aktivitäten der App als Vollbild, immersive und keine Systemnavigationsleisten vorbereitet, sodass der gesamte Bildschirm für die Darstellung gerenderter Teekannen-Frames verwendet werden kann:

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);
}

Native Implementierung

In diesem Abschnitt wird der Teil der Teapot-App erläutert, der in C++ implementiert wurde.

TeapotRenderer.h.

Diese Funktionsaufrufe führen das eigentliche Rendern der Teekanne durch. Sie verwendet ndk_helper für die Matrixberechnung und die Neupositionierung der Kamera je nachdem, worauf die Nutzenden tippen.

ndk_helper::Mat4 mat_projection_;
ndk_helper::Mat4 mat_view_;
ndk_helper::Mat4 mat_model_;


ndk_helper::TapCamera* camera_;

TeekanneNativeActivity.cpp

Die folgenden Zeilen enthalten ndk_helper in der nativen Quelldatei und definieren die Name der Hilfsklasse.

#include "NDKHelper.h"

//-------------------------------------------------------------------------
//Preprocessor
//-------------------------------------------------------------------------
#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper
function

Die erste Verwendung der Klasse ndk_helper besteht darin, EGL-bezogener Lebenszyklus, Verknüpfung von (erstellten/verlorenen) EGL-Kontextstatus mit Android-Lebenszyklusereignisse Die Klasse ndk_helper ermöglicht der Anwendung, den Kontext beizubehalten damit das System eine zerstörte Aktivität wiederherstellen kann. Diese Funktion ist nützlich, wenn die Zielmaschine rotiert wird (wodurch eine Aktivität zerstört und dann sofort in der neuen Ausrichtung wiederhergestellt werden kann, oder wenn das Schloss erscheint.

ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.

Als Nächstes bietet ndk_helper die Touchbedienung.

ndk_helper::DoubletapDetector doubletap_detector_;
ndk_helper::PinchDetector pinch_detector_;
ndk_helper::DragDetector drag_detector_;
ndk_helper::PerfMonitor monitor_;

Außerdem bietet es die Kamerasteuerung (OpenGL-Ansichten).

ndk_helper::TapCamera tap_camera_;

Die App bereitet sich dann auf die Verwendung der Gerätesensoren mithilfe der nativen APIs des NDK vor.

ASensorManager* sensor_manager_;
const ASensor* accelerometer_sensor_;
ASensorEventQueue* sensor_event_queue_;

Die App ruft die folgenden Funktionen als Reaktion auf verschiedene Android- Lebenszyklus-Ereignisse und Änderungen des EGL-Kontextstatus mithilfe verschiedener Funktionen über die Klasse Engine von ndk_helper bereitgestellt.

void LoadResources();
void UnloadResources();
void DrawFrame();
void TermDisplay();
void TrimMemory();
bool IsReady();

Die folgende Funktion ruft dann auf der Java-Seite auf, um die UI-Anzeige zu aktualisieren.

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;
}

Als Nächstes ruft diese Funktion auf der Java-Seite einen Aufruf auf, um ein Textfeld zu zeichnen. wird auf dem Bildschirm der nativen Seite eingeblendet. Anzahl.

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;
}

Die Anwendung ruft die Systemuhr ab und stellt sie dem Renderer bereit. für zeitbasierte Animationen auf Basis der Echtzeituhr. Diese Informationen werden beispielsweise in Impuls berechnet, bei dem die Geschwindigkeit als Funktion der Zeit abnimmt.

renderer_.Update( monitor_.GetCurrentTime() );

Die Anwendung dreht den gerenderten Frame jetzt mithilfe der Funktion GLcontext::Swap() in den Front-Puffer für die Anzeige. behandelt sie auch mögliche Fehler, die während des Umblättern-Vorgangs auftreten können.

if( EGL_SUCCESS != gl_context_->Swap() )  // swaps
buffer.

Das Programm übergibt Berührungsbewegungen an den definierten Gestendetektor. in der Klasse ndk_helper. Die Bewegungserkennung erfasst die Multi-Touch-Funktion zum Beispiel durch Auseinander- und Zusammenziehen der Finger. Außerdem wird eine Benachrichtigung gesendet, wenn alle diese Ereignisse.

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;
}

Die Klasse ndk_helper bietet auch Zugriff auf eine Bibliothek für Vektor-Mathematik (vecmath.h) und verwende es hier zur Umwandlung von Berührungskoordinaten.

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 );
}

Die Methode HandleCmd() verarbeitet Befehle, die vom android_native_app_glue-Bibliothek. Weitere Informationen zu den Nachrichten, finden Sie in den Kommentaren unter android_native_app_glue.h und .c Quelldateien.

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;
    }
}

Der Kurs "ndk_helper" postet APP_CMD_INIT_WINDOW, wenn android_app_glue erhält einen onNativeWindowCreated()-Callback vom System. Anwendungen können normalerweise Fensterinitialisierungen wie EGL ausführen die Initialisierung bei. Dies geschieht außerhalb des Aktivitätslebenszyklus, Aktivität ist noch nicht bereit.

//Init helper functions
ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME );

state->userData = &g_engine;
state->onAppCmd = Engine::HandleCmd;
state->onInputEvent = Engine::HandleInput;