1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
|
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>
|