1 page.title=Sample: Teapot 2 @jd:body 3 4 <div id="qv-wrapper"> 5 <div id="qv"> 6 <h2>On this page</h2> 7 8 <ol> 9 <li><a href="#am">AndroidManifest.xml</a></li> 10 <li><a href="#ap">Application.mk</a></li> 11 <li><a href="#ji">Java-side Implementation</a></li> 12 <li><a href="#ni">Native-side Implementation</a></li> 13 </ol> 14 </li> 15 </ol> 16 </div> 17 </div> 18 19 <p>The Teapot sample is located under in the {@code samples/Teapot/} directory, under the NDK 20 installation's root directory. This sample uses the OpenGL library to render the iconic 21 <a href="http://math.hws.edu/bridgeman/courses/324/s06/doc/opengl.html#basic">Utah 22 teapot</a>. In particular, it showcases the {@code ndk_helper} helper class, 23 a collection of native helper functions required for implementing games and 24 similar applications as native applications. This class provides:</p> 25 26 <ul> 27 <li>An abstraction layer, {@code GLContext}, that handles certain NDK-specific behaviors.</li> 28 <li>Helper functions that are useful but not present in the NDK, such as tap detection.</li> 29 <li>Wrappers for JNI calls for platform features such as texture loading.</li> 30 </ul> 31 32 <h2 id="am">AndroidManifest.xml</h2> 33 <p>The activity declaration here is not {@link android.app.NativeActivity} itself, but 34 a subclass of it: {@code TeapotNativeActivity}.</p> 35 36 <pre class="no-pretty-print"> 37 <activity android:name="com.sample.teapot.TeapotNativeActivity" 38 android:label="@string/app_name" 39 android:configChanges="orientation|keyboardHidden"> 40 </pre> 41 42 <p>Ultimately, the name of the shared-object file that the build system builds is 43 {@code libTeapotNativeActivity.so}. The build system adds the {@code lib} prefix and the {@code .so} 44 extension; neither is part of the value that the manifest originally assigns to 45 {@code android:value}.</p> 46 47 <pre class="no-pretty-print"> 48 <meta-data android:name="android.app.lib_name" 49 android:value="TeapotNativeActivity" /> 50 </pre> 51 52 <h2 id="ap">Application.mk</h2> 53 <p>An app that uses the {@link android.app.NativeActivity} framework class must not specify an 54 Android API level lower than 9, which introduced that class. For more information about the 55 {@link android.app.NativeActivity} class, see 56 <a href="{@docRoot}ndk/guides/concepts.html#naa">Native Activities and Applications</a>. 57 </p> 58 59 <pre class="no-pretty-print"> 60 APP_PLATFORM := android-9 61 </pre> 62 63 <p>The next line tells the build system to build for all supported architectures.</p> 64 <pre class="no-pretty-print"> 65 APP_ABI := all 66 </pre> 67 68 <p>Next, the file tells the build system which 69 <a href="{@docRoot}ndk/guides/cpp-support.html">C++ runtime support library</a> to use. </p> 70 71 <pre class="no-pretty-print"> 72 APP_STL := stlport_static 73 </pre> 74 75 <h2 id="ji">Java-side Implementation</h2> 76 <p>The {@code TeapotNativeActivity.java} file is located in 77 {@code samples/Teapot/src/com/sample/teapot}, under the NDK installation root directory. It handles 78 activity lifecycle events, and also enables the app to display text on the screen. The following 79 block of code is most important from the perspective of the native-side implementation: The native 80 code calls it to display a popup window for displaying text.</p> 81 82 <pre class="no-pretty-print"> 83 84 void setImmersiveSticky() { 85 View decorView = getWindow().getDecorView(); 86 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN 87 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 88 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 89 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 90 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 91 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); 92 } 93 </pre> 94 95 <h2 id="ni">Native-side Implementation</h2> 96 97 <p>This section explores the part of the Teapot app implemented in C++.</p> 98 99 <h3>TeapotRenderer.h</h3> 100 101 <p>These function calls perform the actual rendering of the teapot. It uses 102 {@code ndk_helper} for matrix calculation and to reposition the camera 103 based on where the user taps.</p> 104 105 <pre class="no-pretty-print"> 106 ndk_helper::Mat4 mat_projection_; 107 ndk_helper::Mat4 mat_view_; 108 ndk_helper::Mat4 mat_model_; 109 110 111 ndk_helper::TapCamera* camera_; 112 </pre> 113 114 <h3>TeapotNativeActivity.cpp</h3> 115 116 <p>The following lines include {@code ndk_helper} in the native source file, and define the 117 helper-class name.</p> 118 119 <pre class="no-pretty-print"> 120 121 #include "NDKHelper.h" 122 123 //------------------------------------------------------------------------- 124 //Preprocessor 125 //------------------------------------------------------------------------- 126 #define HELPER_CLASS_NAME "com/sample/helper/NDKHelper" //Class name of helper 127 function 128 </pre> 129 130 <p>The first use of the {@code ndk_helper} class is to handle the 131 EGL-related lifecycle, associating EGL context states (created/lost) with 132 Android lifecycle events. The {@code ndk_helper} class enables the application to preserve context 133 information so that the system can restore a destroyed activity. This ability is useful, for 134 example, when the target machine is rotated (causing an activity to be 135 destroyed, then immediately restored in the new orientation), or when the lock 136 screen appears.</p> 137 138 <pre class="no-pretty-print"> 139 ndk_helper::GLContext* gl_context_; // handles EGL-related lifecycle. 140 </pre> 141 142 <p>Next, {@code ndk_helper} provides touch control.</p> 143 144 <pre class="no-pretty-print"> 145 ndk_helper::DoubletapDetector doubletap_detector_; 146 ndk_helper::PinchDetector pinch_detector_; 147 ndk_helper::DragDetector drag_detector_; 148 ndk_helper::PerfMonitor monitor_; 149 </pre> 150 151 <p>It also provides camera control (openGL view frustum).</p> 152 153 <pre class="no-pretty-print"> 154 ndk_helper::TapCamera tap_camera_; 155 </pre> 156 157 <p>The app then prepares to use the device's sensors, using the native APIs provided in the NDK.</p> 158 159 <pre class="no-pretty-print"> 160 ASensorManager* sensor_manager_; 161 const ASensor* accelerometer_sensor_; 162 ASensorEventQueue* sensor_event_queue_; 163 </pre> 164 165 <p>The app calls the following functions in response to various Android 166 lifecycle events and EGL context state changes, using various functionalities 167 provided by {@code ndk_helper} via the {@code Engine} class.</p> 168 169 <pre class="no-pretty-print"> 170 171 void LoadResources(); 172 void UnloadResources(); 173 void DrawFrame(); 174 void TermDisplay(); 175 void TrimMemory(); 176 bool IsReady(); 177 </pre> 178 179 <p>Then, the following function calls back to the Java side to update the UI display.</p> 180 181 <pre class="no-pretty-print"> 182 void Engine::ShowUI() 183 { 184 JNIEnv *jni; 185 app_->activity->vm->AttachCurrentThread( &jni, NULL ); 186 187 188 //Default class retrieval 189 jclass clazz = jni->GetObjectClass( app_->activity->clazz ); 190 jmethodID methodID = jni->GetMethodID( clazz, "showUI", "()V" ); 191 jni->CallVoidMethod( app_->activity->clazz, methodID ); 192 193 194 app_->activity->vm->DetachCurrentThread(); 195 return; 196 } 197 </pre> 198 199 <p>Next, this function calls back to the Java side to draw a text box 200 superimposed on the screen rendered on the native side, and showing frame 201 count.</p> 202 203 <pre class="no-pretty-print"> 204 void Engine::UpdateFPS( float fFPS ) 205 { 206 JNIEnv *jni; 207 app_->activity->vm->AttachCurrentThread( &jni, NULL ); 208 209 210 //Default class retrieval 211 jclass clazz = jni->GetObjectClass( app_->activity->clazz ); 212 jmethodID methodID = jni->GetMethodID( clazz, "updateFPS", "(F)V" ); 213 jni->CallVoidMethod( app_->activity->clazz, methodID, fFPS ); 214 215 216 app_->activity->vm->DetachCurrentThread(); 217 return; 218 } 219 </pre> 220 221 <p>The application gets the system clock and supplies it to the renderer 222 for time-based animation based on real-time clock. This information is used, for example, in 223 calculating momentum, where speed declines as a function of time.</p> 224 225 <pre class="no-pretty-print"> 226 renderer_.Update( monitor_.GetCurrentTime() ); 227 </pre> 228 229 <p>The application now checks whether the context information that {@code GLcontext} holds is still 230 valid. If not, {@code ndk-helper} swaps the buffer, reinstantiating the GL context.</p> 231 232 <pre class="no-pretty-print"> 233 if( EGL_SUCCESS != gl_context_->Swap() ) // swaps 234 buffer. 235 </pre> 236 237 <p>The program passes touch-motion events to the gesture detector defined 238 in the {@code ndk_helper} class. The gesture detector tracks multitouch 239 gestures, such as pinch-and-drag, and sends a notification when triggered by 240 any of these events.</p> 241 242 <pre class="no-pretty-print"> 243 if( AInputEvent_getType( event ) == AINPUT_EVENT_TYPE_MOTION ) 244 { 245 ndk_helper::GESTURE_STATE doubleTapState = 246 eng->doubletap_detector_.Detect( event ); 247 ndk_helper::GESTURE_STATE dragState = eng->drag_detector_.Detect( event ); 248 ndk_helper::GESTURE_STATE pinchState = eng->pinch_detector_.Detect( event ); 249 250 //Double tap detector has a priority over other detectors 251 if( doubleTapState == ndk_helper::GESTURE_STATE_ACTION ) 252 { 253 //Detect double tap 254 eng->tap_camera_.Reset( true ); 255 } 256 else 257 { 258 //Handle drag state 259 if( dragState & ndk_helper::GESTURE_STATE_START ) 260 { 261 //Otherwise, start dragging 262 ndk_helper::Vec2 v; 263 eng->drag_detector_.GetPointer( v ); 264 eng->TransformPosition( v ); 265 eng->tap_camera_.BeginDrag( v ); 266 } 267 // ...else other possible drag states... 268 269 //Handle pinch state 270 if( pinchState & ndk_helper::GESTURE_STATE_START ) 271 { 272 //Start new pinch 273 ndk_helper::Vec2 v1; 274 ndk_helper::Vec2 v2; 275 eng->pinch_detector_.GetPointers( v1, v2 ); 276 eng->TransformPosition( v1 ); 277 eng->TransformPosition( v2 ); 278 eng->tap_camera_.BeginPinch( v1, v2 ); 279 } 280 // ...else other possible pinch states... 281 } 282 return 1; 283 } 284 </pre> 285 286 <p>The {@code ndk_helper} class also provides access to a vector-math library 287 ({@code vecmath.h}), using it here to transform touch coordinates.</p> 288 289 <pre class="no-pretty-print"> 290 void Engine::TransformPosition( ndk_helper::Vec2& vec ) 291 { 292 vec = ndk_helper::Vec2( 2.0f, 2.0f ) * vec 293 / ndk_helper::Vec2( gl_context_->GetScreenWidth(), 294 gl_context_->GetScreenHeight() ) - ndk_helper::Vec2( 1.f, 1.f ); 295 } 296 </pre> 297 </ul> 298 299 <p>The {@code HandleCmd()} method handles commands posted from the 300 android_native_app_glue library. For more information about what the messages 301 mean, refer to the comments in the {@code android_native_app_glue.h} and 302 {@code .c} source files.</p> 303 304 <pre class="no-pretty-print"> 305 void Engine::HandleCmd( struct android_app* app, 306 int32_t cmd ) 307 { 308 Engine* eng = (Engine*) app->userData; 309 switch( cmd ) 310 { 311 case APP_CMD_SAVE_STATE: 312 break; 313 case APP_CMD_INIT_WINDOW: 314 // The window is being shown, get it ready. 315 if( app->window != NULL ) 316 { 317 eng->InitDisplay(); 318 eng->DrawFrame(); 319 } 320 break; 321 case APP_CMD_TERM_WINDOW: 322 // The window is being hidden or closed, clean it up. 323 eng->TermDisplay(); 324 eng->has_focus_ = false; 325 break; 326 case APP_CMD_STOP: 327 break; 328 case APP_CMD_GAINED_FOCUS: 329 eng->ResumeSensors(); 330 //Start animation 331 eng->has_focus_ = true; 332 break; 333 case APP_CMD_LOST_FOCUS: 334 eng->SuspendSensors(); 335 // Also stop animating. 336 eng->has_focus_ = false; 337 eng->DrawFrame(); 338 break; 339 case APP_CMD_LOW_MEMORY: 340 //Free up GL resources 341 eng->TrimMemory(); 342 break; 343 } 344 } 345 </pre> 346 347 <p>The {@code ndk_helper} class posts {@code APP_CMD_INIT_WINDOW} when {@code android_app_glue} 348 receives an {@code onNativeWindowCreated()} callback from the system. 349 Applications can normally perform window initializations, such as EGL 350 initialization. They do this outside of the activity lifecycle, since the 351 activity is not yet ready.</p> 352 353 <pre class="no-pretty-print"> 354 //Init helper functions 355 ndk_helper::JNIHelper::Init( state->activity, HELPER_CLASS_NAME ); 356 357 state->userData = &g_engine; 358 state->onAppCmd = Engine::HandleCmd; 359 state->onInputEvent = Engine::HandleInput; 360 </pre> 361