Contoh Teapot terletak dalam direktori samples/Teapot/
, pada direktori
root penginstalan NDK. Conoth ini menggunakan library OpenGL untuk merender teapot Utah yang ikonik. Secara khusus, contoh ini memperagakan class helper ndk_helper
, sekumpulan fungsi helper native yang diperlukan untuk mengimplementasikan game dan aplikasi serupa sebagai aplikasi native. Class ini menyediakan:
- Lapisan abstraksi,
GLContext
, yang menangani perilaku spesifik NDK tertentu. - Fungsi helper yang berguna tetapi tidak ada dalam NDK, seperti deteksi ketukan.
- Wrapper untuk panggilan JNI untuk fitur platform seperti pemuatan tekstur.
AndroidManifest.xml
Deklarasi aktivitas di sini bukanlah NativeActivity
itu sendiri, melainkan subclass-nya: TeapotNativeActivity
.
<activity android:name="com.sample.teapot.TeapotNativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
Terakhir, nama file objek bersama yang dibuat oleh sistem build adalah libTeapotNativeActivity.so
. Sistem build menambahkan awalan lib
dan ekstensi .so
; keduanya bukan bagian dari nilai yang pada awalnya ditetapkan manifes ke android:value
.
<meta-data android:name="android.app.lib_name" android:value="TeapotNativeActivity" />
Application.mk
Aplikasi yang menggunakan class framework NativeActivity
tidak boleh menetapkan
API level Android di bawah 9, yang memperkenalkan class tersebut. Untuk mengetahui informasi selengkapnya tentang class NativeActivity
, lihat Aktivitas dan Aplikasi Native.
APP_PLATFORM := android-9
Baris berikut memberi tahu sistem build untuk membuat build bagi semua arsitektur yang didukung.
APP_ABI := all
Selanjutnya, file tersebut memberi tahu sistem build library dukungan runtime C++ yang akan digunakan.
APP_STL := stlport_static
Implementasi sistem Java
FileTeapotNativeActivity
terletak di teapots/classic-teapot/src/com/sample/teapot
, dalam direktori utama repositori NDK di GitHub. File ini menangani peristiwa siklus hidup aktivitas, membuat jendela popup yang menampilkan teks di layar dengan fungsi ShowUI()
, dan memperbarui frekuensi frame secara dinamis dengan fungsi updateFPS()
. Kode berikut mungkin menarik bagi Anda karena menyiapkan Aktivitas aplikasi dalam layar penuh dan imersif tanpa menu navigasi sistem, sehingga seluruh layar dapat digunakan untuk menampilkan frame teapot yang dirender:
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); }
Implementasi sistem native
Bagian ini menjelaskan bagian aplikasi Teapot yang diimplementasikan dalam C++.
TeapotRenderer.h
Panggilan fungsi ini melakukan rendering sesungguhnya untuk teapot. Fungsi ini menggunakan ndk_helper
untuk penghitungan matriks dan untuk mengubah posisi kamera berdasarkan tempat pengguna mengetuk.
ndk_helper::Mat4 mat_projection_; ndk_helper::Mat4 mat_view_; ndk_helper::Mat4 mat_model_; ndk_helper::TapCamera* camera_;
TeapotNativeActivity.cpp
Baris berikut menyertakan ndk_helper
dalam file sumber native, dan menetapkan nama class helper.
#include "NDKHelper.h" //------------------------------------------------------------------------- //Preprocessor //------------------------------------------------------------------------- #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper function
Penggunaan pertama class ndk_helper
adalah untuk menangani siklus proses terkait EGL, yang mengaitkan status konteks EGL (dibuat/dihilangkan) dengan peristiwa siklus proses Android. Class ndk_helper
memungkinkan aplikasi untuk mempertahankan informasi konteks sehingga sistem dapat memulihkan aktivitas yang telah diakhiri. Kemampuan ini sangatlah berguna, misalnya, saat mesin target diputar (yang menyebabkan aktivitas diakhiri, lalu segera dipulihkan dalam orientasi baru), atau saat layar kunci muncul.
ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.
Selanjutnya, ndk_helper
menyediakan kontrol sentuh.
ndk_helper::DoubletapDetector doubletap_detector_; ndk_helper::PinchDetector pinch_detector_; ndk_helper::DragDetector drag_detector_; ndk_helper::PerfMonitor monitor_;
Kode ini juga menyediakan kontrol kamera (frustum tampilan openGL).
ndk_helper::TapCamera tap_camera_;
Selanjutnya, aplikasi bersiap menggunakan sensor perangkat, menggunakan API native yang disediakan dalam NDK.
ASensorManager* sensor_manager_; const ASensor* accelerometer_sensor_; ASensorEventQueue* sensor_event_queue_;
Aplikasi ini memanggil fungsi berikut sebagai respons terhadap berbagai peristiwa siklus proses Android dan perubahan status konteks EGL, menggunakan berbagai fungsionalitas yang disediakan oleh ndk_helper
melalui class Engine
.
void LoadResources(); void UnloadResources(); void DrawFrame(); void TermDisplay(); void TrimMemory(); bool IsReady();
Kemudian, fungsi berikut melakukan callback ke sistem Java untuk memperbarui tampilan 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; }
Selanjutnya, fungsi ini melakukan callback ke sistem Java untuk mengambil kotak teks yang dilapiskan pada layar yang dirender di sistem native, dan menampilkan jumlah frame.
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; }
Aplikasi mendapatkan jam sistem dan memberikannya ke perender untuk animasi berbasis waktu berdasarkan jam real-time. Informasi ini digunakan, misalnya, dalam menghitung momentum, di mana kecepatan menurun sebagai fungsi waktu.
renderer_.Update( monitor_.GetCurrentTime() );
Sekarang aplikasi membalik frame yang dirender ke buffer depan untuk ditampilkan melalui fungsi GLcontext::Swap()
; aplikasi juga menangani kemungkinan error yang terjadi selama proses pembalikan.
if( EGL_SUCCESS != gl_context_->Swap() ) // swaps buffer.
Program ini meneruskan peristiwa gerakan sentuh ke detektor gestur yang ditetapkan dalam class ndk_helper
. Detektor gestur melacak gestur multisentuh, seperti mencubit dan menarik, dan mengirim notifikasi saat dipicu oleh salah satu peristiwa berikut.
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; }
Class ndk_helper
juga menyediakan akses ke library vector-math (vecmath.h
), yang digunakan di sini untuk mengubah koordinat sentuh.
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 ); }
Metode HandleCmd()
menangani perintah yang diposting dari library android_native_app_glue. Untuk mengetahui informasi selengkapnya tentang arti pesan tersebut, lihat komentar di file sumber android_native_app_glue.h
dan .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; } }
Class ndk_helper
memposting APP_CMD_INIT_WINDOW
saat android_app_glue
menerima callback onNativeWindowCreated()
dari sistem.
Aplikasi biasanya dapat melakukan inisialisasi jendela, seperti inisialisasi EGL. Aplikasi melakukan hal itu di luar siklus proses aktivitas, karena aktivitas tersebut belum siap.
//Init helper functions ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME ); state->userData = &g_engine; state->onAppCmd = Engine::HandleCmd; state->onInputEvent = Engine::HandleInput;