Образец: Чайник

Образец Teapot находится в каталоге samples/Teapot/ в корневом каталоге установки NDK. В этом примере используется библиотека OpenGL для визуализации культового чайника штата Юта . В частности, он демонстрирует вспомогательный класс ndk_helper — набор собственных вспомогательных функций, необходимых для реализации игр и подобных приложений в качестве собственных приложений. Этот класс обеспечивает:

  • Уровень абстракции GLContext , который обрабатывает определенное поведение, специфичное для NDK.
  • Вспомогательные функции, которые полезны, но отсутствуют в NDK, например обнаружение касания.
  • Оболочки для 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 ; ни то, ни другое не является частью значения, которое манифест изначально присваивает android:value .

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

Приложение.мк

Приложение, использующее класс платформы NativeActivity не должно указывать уровень Android API ниже 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() . Следующий код может быть вам интересен тем, что он подготавливает активность приложения в полноэкранном режиме, с эффектом погружения и без системных навигационных панелей, чтобы весь экран можно было использовать для отображения визуализированных кадров чайника:

Котлин

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

Ява

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++.

ЧайникRenderer.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_;

Он также обеспечивает управление камерой (усеченный вид 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 для обновления отображения пользовательского интерфейса.

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;