نمونه Teapot در زیر در پوشه samples/Teapot/
، در پوشه ریشه نصب NDK قرار دارد. این نمونه از کتابخانه OpenGL برای ارائه قوری نمادین یوتا استفاده می کند. به طور خاص، کلاس کمکی ndk_helper
را به نمایش میگذارد، مجموعهای از توابع کمکی بومی که برای اجرای بازیها و برنامههای کاربردی مشابه به عنوان برنامههای بومی مورد نیاز است. این کلاس ارائه می دهد:
- یک لایه انتزاعی،
GLContext
، که رفتارهای خاص NDK را کنترل می کند. - توابع کمکی که مفید هستند اما در NDK وجود ندارند، مانند تشخیص ضربه.
- Wrappers برای 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
استفاده میکند نباید سطح API Android کمتر از 9 را که کلاس را معرفی کرده است، مشخص کند. برای اطلاعات بیشتر در مورد کلاس NativeActivity
، به Native Activities and Applications مراجعه کنید.
APP_PLATFORM := android-9
خط بعدی به سیستم ساخت می گوید که برای تمام معماری های پشتیبانی شده بسازد.
APP_ABI := all
در مرحله بعد، فایل به سیستم بیلد می گوید که از کدام کتابخانه پشتیبانی از زمان اجرا C++ استفاده کند.
APP_STL := stlport_static
پیاده سازی سمت جاوا
فایلTeapotNativeActivity
در teapots/classic-teapot/src/com/sample/teapot
، در زیر دایرکتوری ریشه NDK repo در GitHub قرار دارد. رویدادهای چرخه حیات فعالیت را مدیریت می کند، یک پنجره بازشو برای نمایش متن روی صفحه با تابع ShowUI()
ایجاد می کند و نرخ فریم را به صورت پویا با تابع updateFPS()
به روز می کند. کد زیر ممکن است برای شما جالب باشد زیرا Activity برنامه را به صورت تمام صفحه، همه جانبه و بدون نوارهای ناوبری سیستم آماده می کند تا از کل صفحه برای نمایش قاب های قوری رندر شده استفاده شود: کاتلین
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 view frustum) را فراهم می کند.
ndk_helper::TapCamera tap_camera_;
سپس برنامه با استفاده از APIهای بومی ارائه شده در NDK برای استفاده از حسگرهای دستگاه آماده می شود.
ASensorManager* sensor_manager_; const ASensor* accelerometer_sensor_; ASensorEventQueue* sensor_event_queue_;
این برنامه با استفاده از عملکردهای مختلف ارائه شده توسط ndk_helper
از طریق کلاس Engine
، توابع زیر را در پاسخ به رویدادهای مختلف چرخه زندگی Android و تغییرات وضعیت بافت EGL فراخوانی میکند.
void LoadResources(); void UnloadResources(); void DrawFrame(); void TermDisplay(); void TrimMemory(); bool IsReady();
سپس، تابع زیر به سمت جاوا باز می گردد تا نمایشگر رابط کاربری را به روز کند.
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; }
در مرحله بعد، این تابع به سمت جاوا برمی گردد تا یک کادر متنی که روی صفحه نمایش داده شده در سمت اصلی نمایش داده شده است، بکشد و تعداد فریم ها را نشان دهد.
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;