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