يمكنك العثور على عينة إبريق الشاي ضمن الدليل samples/Teapot/
، ضمن الدليل الجذري لتثبيت NDK. يستخدم هذا النموذج مكتبة OpenGL لعرض إبريق شاي في ولاية يوتا الشهير. على وجه الخصوص، يعرض هذا التطبيق فئة المساعدة ndk_helper
، وهي مجموعة من وظائف المساعد المحلي المطلوبة لتشغيل الألعاب والتطبيقات المشابهة مثل التطبيقات الأصلية. ويوفّر هذا الصف ما يلي:
- طبقة تجريد،
GLContext
، تعالج سلوكيات خاصة بـ NDK. - هي وظائف مساعدة مفيدة ولكنها غير متوفّرة في NDK، مثل رصد النقرات.
- تطلب برامج لفّات JNI استخدام ميزات النظام الأساسي مثل تحميل الزخرفة.
ملف AndroidManifest.xml
بيان النشاط هنا ليس NativeActivity
بحد ذاته، ولكن
فئة فرعية منه: TeapotNativeActivity
.
<activity android:name="com.sample.teapot.TeapotNativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
في النهاية، اسم ملف الكائن المشترك الذي ينشئه نظام الإصدار هو libTeapotNativeActivity.so
. يضيف نظام الإصدار البادئة lib
والإضافة .so
،
كما أنّهما لا يشكّلان جزءًا من القيمة التي يعيّنها البيان في الأصل إلى
android:value
.
<meta-data android:name="android.app.lib_name" android:value="TeapotNativeActivity" />
Application.mk
أمّا التطبيق الذي يستخدم فئة إطار العمل NativeActivity
، فيجب ألا يحدّد
مستوى واجهة برمجة تطبيقات Android أقل من 9، والذي قدّم هذه الفئة. للحصول على مزيد من المعلومات حول
الصف NativeActivity
، يُرجى الاطّلاع على
الأنشطة والتطبيقات الأصلية.
APP_PLATFORM := android-9
يخبر السطر التالي نظام الإنشاء بالإنشاء لجميع البنى المتوافقة.
APP_ABI := all
بعد ذلك، يحدّد الملف لنظام الإنشاء مكتبة دعم وقت تشغيل C++ المطلوب استخدامها.
APP_STL := stlport_static
التنفيذ من جهة جافا
يقع الملف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); }
التنفيذ من جهة خارجية
يستكشف هذا القسم جزء تطبيق الشاي الذي تم تنفيذه في C++.
TeaبوتRenderer.h
تؤدي استدعاءات الدالة هذه العرض الفعلي لإبريق الشاي. ويُستخدم
ndk_helper
لحساب المصفوفة وتغيير موضع الكاميرا
بناءً على المكان الذي ينقر عليه المستخدم.
ndk_helper::Mat4 mat_projection_; ndk_helper::Mat4 mat_view_; ndk_helper::Mat4 mat_model_; ndk_helper::TapCamera* camera_;
TeaputNativeActivity.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_;
يستعد التطبيق بعد ذلك لاستخدام أدوات الاستشعار في الجهاز، وذلك باستخدام واجهات برمجة التطبيقات الأصلية التي تم توفيرها في 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;