ตัวอย่างกาน้ำชาอยู่ในไดเรกทอรี samples/Teapot/
ใต้ NDK
ไดเรกทอรีรากของการติดตั้ง ตัวอย่างนี้ใช้ไลบรารี OpenGL เพื่อแสดง
ยูทาห์
กาน้ำชา โดยเฉพาะอย่างยิ่ง แต่ยังแสดงให้เห็นถึงชั้นเรียน
ผู้ช่วย ndk_helper
คอลเล็กชันฟังก์ชันผู้ช่วยเนทีฟที่จำเป็นสำหรับการนำเกมไปใช้และ
แอปพลิเคชันที่คล้ายกับแอปพลิเคชันของระบบ ชั้นเรียนนี้ให้สิ่งต่างๆ ต่อไปนี้
- ซึ่งเป็นเลเยอร์นามธรรม
GLContext
ที่จัดการพฤติกรรมเฉพาะของ NDK บางอย่าง - ฟังก์ชันตัวช่วยที่เป็นประโยชน์แต่ไม่มีอยู่ใน NDK เช่น การตรวจจับการแตะ
- Wrapper สำหรับการเรียก 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
ส่วนขยาย; ไม่ได้เป็นส่วนหนึ่งของค่าที่ไฟล์ Manifest กำหนด
android:value
<meta-data android:name="android.app.lib_name" android:value="TeapotNativeActivity" />
Application.mk
แอปที่ใช้คลาสเฟรมเวิร์ก NativeActivity
ต้องไม่ระบุ
API ของ 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); }
การใช้งานฝั่งเนทีฟ
ส่วนนี้จะสำรวจส่วนของแอป 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_;
กาน้ำชาเนทีฟ NativeActivity.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_;
และยังให้การควบคุมกล้องด้วย (Frustum ด้วยมุมมอง OpenGL)
ndk_helper::TapCamera tap_camera_;
จากนั้นแอปจะเตรียมการใช้เซ็นเซอร์ของอุปกรณ์ โดยใช้ API แบบเนทีฟซึ่งมีให้ใน 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 เพื่ออัปเดตการแสดงผล UI
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;