نمونه Teapot در دایرکتوری samples/Teapot/، زیر دایرکتوری ریشه نصب NDK قرار دارد. این نمونه از کتابخانه OpenGL برای رندر کردن
قوری
Utah نمادین استفاده میکند. بهطور خاص، کلاس کمکی ndk_helper را نشان میدهد،
مجموعهای از توابع کمکی بومی که برای پیادهسازی بازیها و
برنامههای مشابه بهعنوان برنامههای بومی لازم است. این کلاس موارد زیر را ارائه میدهد:
- یک لایه انتزاعی،
GLContext، که برخی رفتارهای خاص کیت توسعه بومی را مدیریت میکند. - توابع کمکی که مفید هستند اما در 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 پایینتر از ۹ را مشخص کند، که این کلاس را معرفی کرده است. برای کسب اطلاعات بیشتر درباره کلاس
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() بهروز میکند. کد زیر ممکن است برای شما جالب باشد زیرا «فعالیت» برنامه را برای تمامصفحه، فراگیر، و بدون نوار پیمایش سیستم آماده میکند تا بتوان از کل صفحه برای نمایش فریمهای قوری رندرشده استفاده کرد:
کاتلین
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 ) }
جاوا
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); }
پیادهسازی سمت بومی
این بخش به بررسی قسمتی از برنامه Teapot میپردازد که در ++C پیادهسازی شده است.
TeapotRenderer.h
این فراخوانیهای تابع، پردازش واقعی قوری را انجام میدهند. از
ndk_helper برای محاسبه ماتریس و تغییر موقعیت دوربین
براساس جایی که کاربر ضربه میزند استفاده میکند.
ndk_helper::Mat4 mat_projection_; ndk_helper::Mat4 mat_view_; ndk_helper::Mat4 mat_model_; ndk_helper::TapCamera* camera_;
TeapotNativeActivity.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;