Örnek: Demlik

Çaydanlık örneği samples/Teapot/ dizininde, NDK yüklemesinin kök dizininde yer alır. Bu örnek, ikonik Utah çaydanlığı oluşturmak için OpenGL kitaplığını kullanır. Özellikle ndk_helper yardımcı sınıfını, oyunları ve benzer uygulamaları yerel uygulamalar olarak uygulamak için gereken yerel yardımcı işlevleri gösterir. Bu sınıf aşağıdakileri sağlar:

  • NDK'ya özgü belirli davranışları işleyen bir soyutlama katmanı (GLContext).
  • Dokunma algılama gibi yararlı olan ancak NDK'da bulunmayan yardımcı işlevler.
  • Doku yükleme gibi platform özelliklerine yönelik JNI çağrıları için sarmalayıcılar.

AndroidManifest.xml

Buradaki etkinlik beyanı NativeActivity değil, bunun bir alt sınıfıdır: TeapotNativeActivity.

<activity android:name="com.sample.teapot.TeapotNativeActivity"
        android:label="@string/app_name"
        android:configChanges="orientation|keyboardHidden">

Sonuç olarak, derleme sisteminin oluşturduğu paylaşılan nesne dosyasının adı libTeapotNativeActivity.so olur. Derleme sistemi, lib önekini ve .so uzantısını ekler. Her ikisi de manifestin başlangıçta android:value'ye atadığı değerin parçası değildir.

<meta-data android:name="android.app.lib_name"
        android:value="TeapotNativeActivity" />

Başvuru.mk

NativeActivity çerçeve sınıfını kullanan bir uygulama, ilgili sınıfı kullanıma sunan 9'dan düşük bir Android API düzeyi belirtmemelidir. NativeActivity sınıfı hakkında daha fazla bilgi için Yerel Etkinlikler ve Uygulamalar bölümüne bakın.

APP_PLATFORM := android-9

Sonraki satır, derleme sistemine desteklenen tüm mimariler için derleme yapacağını belirtir.

APP_ABI := all

Ardından dosya, derleme sistemine hangi C++ çalışma zamanı destek kitaplığının kullanılacağını bildirir.

APP_STL := stlport_static

Java Tarafı Uygulama

TeapotNativeActivity dosyası teapots/classic-teapot/src/com/sample/teapot konumunda, GitHub'daki NDK depo kök dizininin altında yer alır. Etkinlik yaşam döngüsü olaylarını işler, ShowUI() işleviyle ekranda metin görüntülemek için bir pop-up pencere oluşturur ve updateFPS() işleviyle kare hızını dinamik olarak günceller. Aşağıdaki kod ilginizi çekebilir çünkü uygulamanın Etkinliği tam ekran, sürükleyici ve sistem gezinme çubukları olmadan hazırlanır, böylece oluşturulan çaydanlık karelerini göstermek için tüm ekran kullanılabilir:

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);
}

Yerel Taraf Uygulama

Bu bölümde, Çaydanlık uygulamasının C++'ta uygulanan bölümü incelenmektedir.

ÇaydanlıkRenderer.h

Bu işlev çağrıları, çaydanlığın gerçek oluşturulmasını gerçekleştirir. Matris hesaplaması ve kullanıcının dokunduğu yere göre kamerayı yeniden konumlandırmak için ndk_helper kullanır.

ndk_helper::Mat4 mat_projection_;
ndk_helper::Mat4 mat_view_;
ndk_helper::Mat4 mat_model_;


ndk_helper::TapCamera* camera_;

ÇaydanlıkNativeActivity.cpp

Aşağıdaki satırlar yerel kaynak dosyada ndk_helper içerir ve yardımcı sınıf adını tanımlar.


#include "NDKHelper.h"

//-------------------------------------------------------------------------
//Preprocessor
//-------------------------------------------------------------------------
#define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper
function

ndk_helper sınıfının ilk kullanımı, EGL ile ilgili yaşam döngüsünü ele alarak EGL bağlamı durumlarını (oluşturulan/kaybedilen) Android yaşam döngüsü olaylarıyla ilişkilendirmektir. ndk_helper sınıfı, uygulamanın yok edilmiş bir etkinliği geri yükleyebilmesi için bağlam bilgilerini koruyabilmesini sağlar. Bu özellik, örneğin hedef makine döndürüldüğünde (bir etkinliğin kaldırılması, ardından hemen yeni yönde geri yüklenmesi) veya kilit ekranı göründüğünde yararlıdır.

ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle.

ndk_helper ayrıca dokunmatik kontrol sağlar.

ndk_helper::DoubletapDetector doubletap_detector_;
ndk_helper::PinchDetector pinch_detector_;
ndk_helper::DragDetector drag_detector_;
ndk_helper::PerfMonitor monitor_;

Aynı zamanda kamera kontrolü (openGL view frustum) sağlar.

ndk_helper::TapCamera tap_camera_;

Daha sonra uygulama, NDK'da sağlanan yerel API'lerden yararlanarak cihazın sensörlerini kullanmaya hazırlanır.

ASensorManager* sensor_manager_;
const ASensor* accelerometer_sensor_;
ASensorEventQueue* sensor_event_queue_;

Uygulama, ndk_helper tarafından Engine sınıfı aracılığıyla sağlanan çeşitli işlevleri kullanarak çeşitli Android yaşam döngüsü olaylarına ve EGL bağlam durumu değişikliklerine yanıt olarak aşağıdaki işlevleri çağırır.


void LoadResources();
void UnloadResources();
void DrawFrame();
void TermDisplay();
void TrimMemory();
bool IsReady();

Ardından, aşağıdaki işlev, kullanıcı arayüzü görüntüsünü güncellemek için Java tarafına geri çağırır.

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;
}

Daha sonra bu işlev, yerel tarafta oluşturulan ekranın üzerine binen ve kare sayısını gösteren bir metin kutusu çizmek için Java tarafına geri çağrı yapar.

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;
}

Uygulama, sistem saatini alır ve gerçek zamanlı saate dayalı, zamana dayalı animasyon için bunu oluşturucuya sağlar. Bu bilgiler, örneğin, hızın zamanın bir fonksiyonu olarak düştüğü momentumu hesaplarken kullanılır.

renderer_.Update( monitor_.GetCurrentTime() );

Uygulama artık oluşturulan kareyi GLcontext::Swap() işlevi aracılığıyla görüntüleme için ön arabelleğe çeviriyor; ayrıca çevirme işlemi sırasında oluşan olası hataları da ele alıyor.

if( EGL_SUCCESS != gl_context_->Swap() )  // swaps
buffer.

Program, dokunma hareketi etkinliklerini ndk_helper sınıfında tanımlanan hareket algılayıcıya iletir. Hareket algılayıcı, sıkıştırma ve sürükleme gibi çoklu dokunma hareketlerini izler ve bu etkinliklerden biri tetiklendiğinde bir bildirim gönderir.

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 sınıfı ayrıca, dokunma koordinatlarını dönüştürmek için burada kullanılan bir vektör-matematik kitaplığına (vecmath.h) erişim sağlar.

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() yöntemi, android_native_app_glue kitaplığından gönderilen komutları işler. Mesajların ne anlama geldiği hakkında daha fazla bilgi için android_native_app_glue.h ve .c kaynak dosyalarındaki yorumlara bakın.

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;
    }
}

android_app_glue sistemden onNativeWindowCreated() geri çağırması aldığında ndk_helper sınıfı APP_CMD_INIT_WINDOW değerini yayınlar. Uygulamalar normalde EGL başlatma gibi pencere başlatma işlemleri gerçekleştirebilir. Bunu etkinlik yaşam döngüsünün dışında yaparlar, çünkü etkinlik henüz hazır değildir.

//Init helper functions
ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME );

state->userData = &g_engine;
state->onAppCmd = Engine::HandleCmd;
state->onInputEvent = Engine::HandleInput;