Teapot 示例位于 NDK 安装根目录下的 samples/Teapot/
目录内。 此示例使用 OpenGL 库渲染标志性的犹他茶壶。 特别值得注意的是,此示例展示 ndk_helper
帮助程序类,其中包含的许多原生帮助程序函数是以原生应用的形式实现游戏和类似应用所必需的。 此类提供:
- 用于处理某些 NDK 特有行为的抽象层
GLContext
。 - 虽然很有用,但 NDK 中却不存在的一些帮助程序函数(例如,点按侦测)。
- 供 JNI 用于调用平台功能(例如,材质加载)的包装器。
AndroidManifest.xml
此处的 Activity 声明本身并不是 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" />
Application.mk
使用 NativeActivity
框架类的应用不得指定级别 9 以下的 Android API 级别(从级别 9 开始提供该框架类)。 如需了解有关 NativeActivity
类的详细信息,请参阅原生 Activity 和应用。
APP_PLATFORM := android-9
下一行指示构建系统为所有受支持的架构实施构建。
APP_ABI := all
然后,该文件告知构建系统应使用哪个 C++ 运行时支持库。
APP_STL := stlport_static
Java 端实现
TeapotNativeActivity.java
文件位于 NDK 安装根目录下的 samples/Teapot/src/com/sample/teapot
内。 它用于处理 Activity 生命周期事件,以及帮助应用在屏幕上显示文本。 从原生端实现的角度来看,以下代码块最为重要: 原生代码调用该代码块呈现用于显示文本的弹出窗口。
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_;
TeapotNativeActivity.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
类让应用能够保存上下文信息,以供系统用于恢复销毁的 Activity。 举例来说,当目标机器旋转(导致 Activity 被销毁,然后在新的屏幕方向立即恢复)或出现锁定屏幕时,此功能很有用。
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_;
应用接下来准备通过 NDK 中提供的原生 API 使用设备的传感器。
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
保存的上下文信息是否仍然有效。 如果不再有效,ndk-helper
会交换缓冲区,重新实例化 GL 上下文。
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; } }
android_app_glue
从系统收到 onNativeWindowCreated()
回调时,ndk_helper
类发出 APP_CMD_INIT_WINDOW
命令。应用可以正常执行窗口初始化,例如 EGL 初始化。 应用在活动生命周期之外执行此项工作,因为 Activity 此时尚未就绪。
//Init helper functions ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME ); state->userData = &g_engine; state->onAppCmd = Engine::HandleCmd; state->onInputEvent = Engine::HandleInput;