| page.title=Sample: Teapot |
| @jd:body |
| |
| <div id="qv-wrapper"> |
| <div id="qv"> |
| <h2>On this page</h2> |
| |
| <ol> |
| <li><a href="#am">AndroidManifest.xml</a></li> |
| <li><a href="#ap">Application.mk</a></li> |
| <li><a href="#ji">Java-side Implementation</a></li> |
| <li><a href="#ni">Native-side Implementation</a></li> |
| </ol> |
| </li> |
| </ol> |
| </div> |
| </div> |
| |
| <p>The Teapot sample is located under in the {@code samples/Teapot/} directory, under the NDK |
| installation's root directory. This sample uses the OpenGL library to render the iconic |
| <a href="http://math.hws.edu/bridgeman/courses/324/s06/doc/opengl.html#basic">Utah |
| teapot</a>. In particular, it showcases the {@code ndk_helper} helper class, |
| a collection of native helper functions required for implementing games and |
| similar applications as native applications. This class provides:</p> |
| |
| <ul> |
| <li>An abstraction layer, {@code GLContext}, that handles certain NDK-specific behaviors.</li> |
| <li>Helper functions that are useful but not present in the NDK, such as tap detection.</li> |
| <li>Wrappers for JNI calls for platform features such as texture loading.</li> |
| </ul> |
| |
| <h2 id="am">AndroidManifest.xml</h2> |
| <p>The activity declaration here is not {@link android.app.NativeActivity} itself, but |
| a subclass of it: {@code TeapotNativeActivity}.</p> |
| |
| <pre class="no-pretty-print"> |
| <activity android:name="com.sample.teapot.TeapotNativeActivity" |
| android:label="@string/app_name" |
| android:configChanges="orientation|keyboardHidden"> |
| </pre> |
| |
| <p>Ultimately, the name of the shared-object file that the build system builds is |
| {@code libTeapotNativeActivity.so}. The build system adds the {@code lib} prefix and the {@code .so} |
| extension; neither is part of the value that the manifest originally assigns to |
| {@code android:value}.</p> |
| |
| <pre class="no-pretty-print"> |
| <meta-data android:name="android.app.lib_name" |
| android:value="TeapotNativeActivity" /> |
| </pre> |
| |
| <h2 id="ap">Application.mk</h2> |
| <p>An app that uses the {@link android.app.NativeActivity} framework class must not specify an |
| Android API level lower than 9, which introduced that class. For more information about the |
| {@link android.app.NativeActivity} class, see |
| <a href="{@docRoot}ndk/guides/concepts.html#naa">Native Activities and Applications</a>. |
| </p> |
| |
| <pre class="no-pretty-print"> |
| APP_PLATFORM := android-9 |
| </pre> |
| |
| <p>The next line tells the build system to build for all supported architectures.</p> |
| <pre class="no-pretty-print"> |
| APP_ABI := all |
| </pre> |
| |
| <p>Next, the file tells the build system which |
| <a href="{@docRoot}ndk/guides/cpp-support.html">C++ runtime support library</a> to use. </p> |
| |
| <pre class="no-pretty-print"> |
| APP_STL := stlport_static |
| </pre> |
| |
| <h2 id="ji">Java-side Implementation</h2> |
| <p>The {@code TeapotNativeActivity.java} file is located in |
| {@code samples/Teapot/src/com/sample/teapot}, under the NDK installation root directory. It handles |
| activity lifecycle events, and also enables the app to display text on the screen. The following |
| block of code is most important from the perspective of the native-side implementation: The native |
| code calls it to display a popup window for displaying text.</p> |
| |
| <pre class="no-pretty-print"> |
| |
| 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); |
| } |
| </pre> |
| |
| <h2 id="ni">Native-side Implementation</h2> |
| |
| <p>This section explores the part of the Teapot app implemented in C++.</p> |
| |
| <h3>TeapotRenderer.h</h3> |
| |
| <p>These function calls perform the actual rendering of the teapot. It uses |
| {@code ndk_helper} for matrix calculation and to reposition the camera |
| based on where the user taps.</p> |
| |
| <pre class="no-pretty-print"> |
| ndk_helper::Mat4 mat_projection_; |
| ndk_helper::Mat4 mat_view_; |
| ndk_helper::Mat4 mat_model_; |
| |
| |
| ndk_helper::TapCamera* camera_; |
| </pre> |
| |
| <h3>TeapotNativeActivity.cpp</h3> |
| |
| <p>The following lines include {@code ndk_helper} in the native source file, and define the |
| helper-class name.</p> |
| |
| <pre class="no-pretty-print"> |
| |
| #include "NDKHelper.h" |
| |
| //------------------------------------------------------------------------- |
| //Preprocessor |
| //------------------------------------------------------------------------- |
| #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper |
| function |
| </pre> |
| |
| <p>The first use of the {@code ndk_helper} class is to handle the |
| EGL-related lifecycle, associating EGL context states (created/lost) with |
| Android lifecycle events. The {@code ndk_helper} class enables the application to preserve context |
| information so that the system can restore a destroyed activity. This ability is useful, for |
| example, when the target machine is rotated (causing an activity to be |
| destroyed, then immediately restored in the new orientation), or when the lock |
| screen appears.</p> |
| |
| <pre class="no-pretty-print"> |
| ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle. |
| </pre> |
| |
| <p>Next, {@code ndk_helper} provides touch control.</p> |
| |
| <pre class="no-pretty-print"> |
| ndk_helper::DoubletapDetector doubletap_detector_; |
| ndk_helper::PinchDetector pinch_detector_; |
| ndk_helper::DragDetector drag_detector_; |
| ndk_helper::PerfMonitor monitor_; |
| </pre> |
| |
| <p>It also provides camera control (openGL view frustum).</p> |
| |
| <pre class="no-pretty-print"> |
| ndk_helper::TapCamera tap_camera_; |
| </pre> |
| |
| <p>The app then prepares to use the device's sensors, using the native APIs provided in the NDK.</p> |
| |
| <pre class="no-pretty-print"> |
| ASensorManager* sensor_manager_; |
| const ASensor* accelerometer_sensor_; |
| ASensorEventQueue* sensor_event_queue_; |
| </pre> |
| |
| <p>The app calls the following functions in response to various Android |
| lifecycle events and EGL context state changes, using various functionalities |
| provided by {@code ndk_helper} via the {@code Engine} class.</p> |
| |
| <pre class="no-pretty-print"> |
| |
| void LoadResources(); |
| void UnloadResources(); |
| void DrawFrame(); |
| void TermDisplay(); |
| void TrimMemory(); |
| bool IsReady(); |
| </pre> |
| |
| <p>Then, the following function calls back to the Java side to update the UI display.</p> |
| |
| <pre class="no-pretty-print"> |
| 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; |
| } |
| </pre> |
| |
| <p>Next, this function calls back to the Java side to draw a text box |
| superimposed on the screen rendered on the native side, and showing frame |
| count.</p> |
| |
| <pre class="no-pretty-print"> |
| 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; |
| } |
| </pre> |
| |
| <p>The application gets the system clock and supplies it to the renderer |
| for time-based animation based on real-time clock. This information is used, for example, in |
| calculating momentum, where speed declines as a function of time.</p> |
| |
| <pre class="no-pretty-print"> |
| renderer_.Update( monitor_.GetCurrentTime() ); |
| </pre> |
| |
| <p>The application now checks whether the context information that {@code GLcontext} holds is still |
| valid. If not, {@code ndk-helper} swaps the buffer, reinstantiating the GL context.</p> |
| |
| <pre class="no-pretty-print"> |
| if( EGL_SUCCESS != gl_context_->Swap() ) // swaps |
| buffer. |
| </pre> |
| |
| <p>The program passes touch-motion events to the gesture detector defined |
| in the {@code ndk_helper} class. The gesture detector tracks multitouch |
| gestures, such as pinch-and-drag, and sends a notification when triggered by |
| any of these events.</p> |
| |
| <pre class="no-pretty-print"> |
| 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; |
| } |
| </pre> |
| |
| <p>The {@code ndk_helper} class also provides access to a vector-math library |
| ({@code vecmath.h}), using it here to transform touch coordinates.</p> |
| |
| <pre class="no-pretty-print"> |
| 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 ); |
| } |
| </pre> |
| </ul> |
| |
| <p>The {@code HandleCmd()} method handles commands posted from the |
| android_native_app_glue library. For more information about what the messages |
| mean, refer to the comments in the {@code android_native_app_glue.h} and |
| {@code .c} source files.</p> |
| |
| <pre class="no-pretty-print"> |
| 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; |
| } |
| } |
| </pre> |
| |
| <p>The {@code ndk_helper} class posts {@code APP_CMD_INIT_WINDOW} when {@code android_app_glue} |
| receives an {@code onNativeWindowCreated()} callback from the system. |
| Applications can normally perform window initializations, such as EGL |
| initialization. They do this outside of the activity lifecycle, since the |
| activity is not yet ready.</p> |
| |
| <pre class="no-pretty-print"> |
| //Init helper functions |
| ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME ); |
| |
| state->userData = &g_engine; |
| state->onAppCmd = Engine::HandleCmd; |
| state->onInputEvent = Engine::HandleInput; |
| </pre> |