ตัวอย่าง: กาน้ำชา

ตัวอย่างกาน้ำชาอยู่ในไดเรกทอรี 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;