العيّنة: إبريق شاي

تتوفّر عيّنة 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;