Contoh: Teapot

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++ mana yang akan digunakan.

APP_STL := stlport_static

Implementasi sistem Java

File TeapotNativeActivity 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;