דוגמת שתיית התה ממוקמת בספרייה samples/Teapot/
, תחת NDK
בתיקיית השורש של ההתקנה. בדוגמה הזו נעשה שימוש בספריית OpenGL כדי לעבד את
יוטה
קנקן תה. באופן ספציפי, מוצגת המחלקה המסייעת של ndk_helper
,
אוסף של פונקציות עזר מותאמות שנדרשות להטמעת משחקים
שדומות לאלה של אפליקציות נייטיב. הקורס הזה מספק:
- שכבת הפשטה,
GLContext
, שמטפלת בהתנהגויות מסוימות שספציפיות ל-NDK. - פונקציות מסייעות שימושיות אבל לא קיימות ב-NDK, כמו זיהוי הקשה.
- רכיבי wrapper לקריאות JNI עבור תכונות פלטפורמה כמו טעינת טקסטורה.
AndroidManifest.xml
הצהרת הפעילות כאן היא לא NativeActivity
עצמה, אבל
תת-מחלקה שלו: TeapotNativeActivity
.
<activity android:name="com.sample.teapot.TeapotNativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
בסופו של דבר, השם של קובץ האובייקט המשותף שמערכת ה-build יוצרת הוא
libTeapotNativeActivity.so
מערכת ה-build מוסיפה את הקידומת lib
ואת .so
סיומת; אף אחת מהאפשרויות האלה אינה חלק מהערך שהמניפסט מקצה לו במקור
android:value
.
<meta-data android:name="android.app.lib_name" android:value="TeapotNativeActivity" />
Application.mk
אפליקציה שמשתמשת במחלקת framework של NativeActivity
לא יכולה לציין
רמת ה-API של Android נמוכה מ-9, שבה נוסף המחלקה הזאת. לקבלת מידע נוסף על
כיתה אחת (NativeActivity
), לעיון
אפליקציות ופעילויות נייטיב.
APP_PLATFORM := android-9
השורה הבאה מנחה את מערכת ה-build לבנות לכל הארכיטקטורות הנתמכות.
APP_ABI := all
לאחר מכן הקובץ מורה למערכת ה-build ספריית תמיכה בסביבת זמן ריצה ב-C++.
APP_STL := stlport_static
הטמעה בצד Java
הקובץTeapotNativeActivity
נמצא ב-teapots/classic-teapot/src/com/sample/teapot
, בספריית השורש של המאגר NDK ב-GitHub. השירות מטפל באירועי מחזור חיים של פעילות, יוצר חלון קופץ להצגת טקסט במסך עם הפונקציה ShowUI()
ומעדכנים את קצב הפריימים באופן דינמי באמצעות הפונקציה updateFPS()
. הקוד הבא עשוי לעניין אתכם בכך שהוא מכין את הפעילות של האפליקציה למסך מלא, סוחפת וללא סרגלי ניווט של המערכת, כך שניתן יהיה להשתמש בכל המסך להצגת הפריימים של קנקן תה:
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); }
הטמעה בצד המודעות
בקטע הזה מתואר החלק של אפליקציית Teabot שמוטמעת ב-C++.
Teaspot Renderer.h
הקריאות לפונקציה מבצעות את העיבוד בפועל של קנקן התה. הוא משתמש
ndk_helper
לחישוב המטריצה ולמיקום המצלמה מחדש
בהתאם למקום שבו המשתמש מקיש.
ndk_helper::Mat4 mat_projection_; ndk_helper::Mat4 mat_view_; ndk_helper::Mat4 mat_model_; ndk_helper::TapCamera* camera_;
TeabotNativeActivity.cpp
השורות הבאות כוללות את ndk_helper
בקובץ המקור המקורי, ומגדירות את
שם של מחלקה משנית.
#include "NDKHelper.h" //------------------------------------------------------------------------- //Preprocessor //------------------------------------------------------------------------- #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
השימוש הראשון במחלקה ndk_helper
הוא לטפל
מחזור חיים שקשור ל-EGL, שיוך מצבי הקשר של EGL (נוצר/אבד) עם
אירועים במחזור החיים של Android. המחלקה ndk_helper
מאפשרת לאפליקציה לשמור על ההקשר
כדי שהמערכת תוכל לשחזר פעילות שהושחתה. היכולת הזו שימושית לצורך
לדוגמה, כשמתבצעת רוטציה של מכונת היעד (וכתוצאה מכך פעילות
יושמד ואז ישוחזר מיד בכיוון החדש), או כשהמנעול
מופיעה.
ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.
בשלב הבא, התכונה ndk_helper
מספקת שליטה באמצעות מגע.
ndk_helper::DoubletapDetector doubletap_detector_; ndk_helper::PinchDetector pinch_detector_; ndk_helper::DragDetector drag_detector_; ndk_helper::PerfMonitor monitor_;
הוא גם מספק שליטה במצלמה (תסכול תצוגת openGL).
ndk_helper::TapCamera tap_camera_;
לאחר מכן, האפליקציה מתכוננת לשימוש בחיישני המכשיר באמצעות ממשקי ה-API המקוריים שסופקו ב-NDK.
ASensorManager* sensor_manager_; const ASensor* accelerometer_sensor_; ASensorEventQueue* sensor_event_queue_;
האפליקציה מפעילה את הפונקציות הבאות בתגובה למערכות Android שונות
אירועים במחזור חיים ושינויים במצב ההקשר של EGL, באמצעות פונקציות שונות
סופק על ידי ndk_helper
באמצעות הכיתה Engine
.
void LoadResources(); void UnloadResources(); void DrawFrame(); void TermDisplay(); void TrimMemory(); bool IsReady();
לאחר מכן, הפונקציה הבאה מפעילה חזרה לצד ה-Java כדי לעדכן את תצוגת ממשק המשתמש.
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; }
בשלב הבא, הפונקציה קוראת חזרה לצד Java כדי לשרטט תיבת טקסט שמופיע על המסך שמעובד בצד ימין, ומראה מסגרת לספור.
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; }
האפליקציה מקבלת את שעון המערכת ומספקת אותו לרינדור לאנימציה מבוססת-זמן המבוססת על שעון בזמן אמת. המידע הזה משמש, לדוגמה, חישוב התנע, שבו המהירות יורדת כפונקציה של זמן.
renderer_.Update( monitor_.GetCurrentTime() );
האפליקציה הופכת עכשיו את המסגרת שעבר רינדור למאגר הנתונים הקדמי לצורך תצוגה באמצעות הפונקציה GLcontext::Swap()
. הוא גם מטפל בשגיאות אפשריות שהתרחשו במהלך תהליך ההיפוך.
if( EGL_SUCCESS != gl_context_->Swap() ) // swaps buffer.
התוכנית מעבירה אירועי מגע בתנועה אל מזהה התנועה שהוגדר
בכיתה ndk_helper
. זיהוי התנועה עוקב אחר ריבוי נקודות מגע
תנועות, כמו צביטה וגרירה, ושולחת הודעה כאשר הן מופעלות על ידי
כל אחד מהאירועים האלה.
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; }
המחלקה ndk_helper
מספקת גם גישה לספריית וקטור מתמטיקה
(vecmath.h
), משתמשים בו כאן כדי לשנות קואורדינטות מגע.
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 ); }
השיטה HandleCmd()
מטפלת בפקודות שפורסמו מ
ספריית android_native_app_glue. למידע נוסף על ההודעות
כלומר, אפשר לעיין בהערות שבandroid_native_app_glue.h
.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; } }
הכיתה 'ndk_helper
' מפרסמת את APP_CMD_INIT_WINDOW
בתאריך android_app_glue
מקבל מהמערכת קריאה חוזרת של onNativeWindowCreated()
.
בדרך כלל אפליקציות יכולות לבצע אתחולי חלונות, כמו EGL
באתחול. הם עושים זאת מחוץ למחזור החיים של הפעילות,
הפעילות עדיין אינה מוכנה.
//Init helper functions ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME ); state->userData = &g_engine; state->onAppCmd = Engine::HandleCmd; state->onInputEvent = Engine::HandleInput;