تتوفّر عيّنة Teabot ضمن الدليل 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" />
تطبيق.mk
يجب ألا يحدّد التطبيق الذي يستخدم فئة إطار العمل NativeActivity
مستوى واجهة برمجة تطبيقات Android أقل من 9، وقد تم تضمين هذه الفئة في هذا الإصدار. لمزيد من المعلومات عن
صف واحد (NativeActivity
)، يُرجى الاطّلاع على
الأنشطة والتطبيقات الأصلية:
APP_PLATFORM := android-9
السطر التالي يخبر نظام التصميم بالإنشاء لجميع البُنى الأساسية المتوافقة.
APP_ABI := all
بعد ذلك، يخبر الملف نظام الإصدار مكتبة دعم بيئة تشغيل 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++.
TeabotRenderer.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_;
يستعد التطبيق بعد ذلك لاستخدام أدوات الاستشعار في الجهاز باستخدام واجهات برمجة التطبيقات الأصلية المتوفّرة في اتفاقية عدم الإفصاح.
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;