Przykład: Imbryk

Przykładowy plik Teapot znajduje się w katalogu samples/Teapot/, w katalogu NDK w katalogu głównym instalacji. W tym przykładzie korzystamy z biblioteki OpenGL do renderowania Utah imbryk. W szczególności pokazuje on klasę pomocniczą ndk_helper, zestawu natywnych funkcji pomocniczych wymaganych do implementacji gier oraz podobne do aplikacji natywnych. Wykłady te obejmują:

  • Warstwa abstrakcji (GLContext), która obsługuje określone zachowania specyficzne dla NDK.
  • Funkcje pomocnicze, które są przydatne, ale nie ma ich w NDK, takie jak wykrywanie kliknięć.
  • Paczki wywołań JNI na potrzeby funkcji platformy, takich jak wczytywanie tekstury.

AndroidManifest.xml

Deklaracja aktywności nie dotyczy samego elementu NativeActivity, ale jej podklasę: TeapotNativeActivity.

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

Ostatecznie nazwa pliku shared-object, który kompiluje system kompilacji, to libTeapotNativeActivity.so System kompilacji dodaje prefiks lib i .so rozszerzenie; żadna z tych opcji nie jest częścią wartości, do której plik manifestu został pierwotnie przypisany android:value

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

Aplikacja.mk

Aplikacja, która używa klasy platformy NativeActivity, nie może określać klasy platformy Poziom interfejsu API Androida niższy niż 9, w którym wprowadzono tę klasę. Więcej informacji na temat NativeActivity zajęcia, zobacz Aktywności i aplikacje natywne.

APP_PLATFORM := android-9

Następny wiersz informuje system kompilacji, że ma kompilować dla wszystkich obsługiwanych architektur.

APP_ABI := all

Następnie plik informuje system kompilacji, który Biblioteka pomocy środowiska wykonawczego C++.

APP_STL := stlport_static

Implementacja po stronie Javy

Plik TeapotNativeActivity znajduje się w katalogu teapots/classic-teapot/src/com/sample/teapot, w katalogu głównym repozytorium NDK na GitHubie. Obsługuje zdarzenia cyklu życia aktywności, tworzy wyskakujące okienko w celu wyświetlania tekstu na ekranie za pomocą funkcji ShowUI() i dynamicznie aktualizuje liczbę klatek za pomocą funkcji updateFPS(). Może Cię zainteresować ten kod, ponieważ przygotowuje aktywność w aplikacji do wyświetlania na pełnym ekranie, w rzeczywistości bez pasków nawigacyjnych systemu i bez pasków nawigacyjnych systemu, tak aby na całym ekranie można było wyświetlać wyrenderowane ramki do herbaty:

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

Implementacja po stronie natywnej

W tej sekcji omawiamy część aplikacji Teapot zaimplementowaną w języku C++.

TeapotRenderer.h

Te wywołania funkcji wykonują rzeczywiste wyrenderowanie czajnika. Wykorzystuje ndk_helper, aby obliczyć matrycę i zmienić położenie kamery w zależności od tego, gdzie użytkownik kliknie reklamę.

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


ndk_helper::TapCamera* camera_;

TeapotNativeActivity.cpp

Te wiersze zawierają fragment ndk_helper w natywnym pliku źródłowym oraz definiują nazwę klasy pomocniczej.

#include "NDKHelper.h"

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

Pierwszym zastosowaniem klasy ndk_helper jest obsługa Cykl życia związany z EGL, łączenie stanów kontekstu EGL (utworzone/utracone) z Zdarzenia cyklu życia Androida. Klasa ndk_helper umożliwia aplikacji zachowywanie kontekstu umożliwiające systemowi przywrócenie zniszczonej aktywności. Ta umiejętność jest przydatna, gdy: np. gdy komputer docelowy jest obrócony (powodując zniszczenia i natychmiastowego przywrócenia w nowej orientacji) lub po otwarciu zamka ekranu.

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

Następnie ndk_helper umożliwia sterowanie dotykowe.

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

Zapewnia też możliwość sterowania kamerą (openGL view frustum).

ndk_helper::TapCamera tap_camera_;

Następnie aplikacja przygotowuje się do użycia czujników urządzenia, korzystając z natywnych interfejsów API dostępnych w NDK.

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

Aplikacja wywołuje te funkcje w odpowiedzi na różne urządzenia z Androidem zdarzenia cyklu życia i zmiany stanu kontekstu EGL przy użyciu różnych funkcji źródło: ndk_helper za pomocą klasy Engine.

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

Następnie funkcja poniżej wywołuje stronę Java, aby zaktualizować interfejs użytkownika.

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

Następnie ta funkcja wywołuje stronę Java, aby narysować pole tekstowe. nałożone na ekran renderowany po stronie natywnej i zawierające ramkę .

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

Aplikacja pobiera zegar systemowy i przekazuje go do mechanizmu renderowania w przypadku animacji opartych na czasie na podstawie zegara w czasie rzeczywistym. Te informacje są używane na przykład w nagłówku obliczanie pędu, gdzie prędkość spada jako funkcja czasu.

renderer_.Update( monitor_.GetCurrentTime() );

Aplikacja używa teraz funkcji GLcontext::Swap(), aby odwrócić wyrenderowaną ramkę do przedniego bufora w celu wyświetlenia. obsługuje też potencjalne błędy, które wystąpiły podczas odwracania stron.

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

Program przekazuje zdarzenia ruchu dotyku do zdefiniowanego detektora gestów w zajęciach ndk_helper. Detektor gestów śledzi wielodotyk takich jak ściągnięcie i przeciągnięcie, oraz wysyła powiadomienie po wywołaniu przez dowolnego z tych zdarzeń.

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

Zajęcia ndk_helper zapewniają też dostęp do biblioteki matematyki wektorowej (vecmath.h) na jego podstawie.

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

Metoda HandleCmd() obsługuje polecenia publikowane z poziomu android_native_app_glue. Więcej informacji na temat tego, czym są wiadomości zajrzyj do komentarzy w android_native_app_glue.h Pliki źródłowe: .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;
    }
}

Zajęcia ndk_helper publikują APP_CMD_INIT_WINDOW, gdy android_app_glue otrzymuje wywołanie zwrotne onNativeWindowCreated() z systemu. Aplikacje zwykle mogą inicjować okna, np. EGL jego zainicjowanie. Robią to poza cyklem aktywności, ponieważ aktywność nie jest jeszcze gotowa.

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

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