NDK示例:Teapot

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;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值