Образец 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;