NDK Programmer's Guide
|
This is a very simple example of a purely native application, with no Java source code. In the absence of any Java source, the Java compiler still creates an executable stub for the Dalvik Virtual Machine ("DVM") to run. The stub serves as a wrapper for the actual, native program, which lives in the .so file.
The application itself simply renders a color onto the entire screen, and then changes the color partly in response to detected movement.
Make sure not to specify an Android API level lower than 9.
<uses-sdk android:minSdkVersion="9" />
Because this application has only native code, specify
android:hasCode
as false
.
<application android:label="@string/app_name" android:hasCode="false">
Declare the NativeActivity
class.
<activity android:name="android.app.NativeActivity"
For android:value
, provide the name of the shared library
to be built, minus the initial lib
and the .so
extension. This value must be the same as the one you described for
LOCAL_MODULE
in Android.mk
.
<meta-data android:name="android.app.lib_name" android:value="native-activity" />
This file tells the build system the following information:
The name of the shared library to generate.
LOCAL_MODULE := native-activity
The name of the native source-code file.
LOCAL_SRC_FILES := main.c
A list of external libraries that will be used in building the binary,
each preceded by the -l
(link-against) option.
Note that, for each library:
lib
, and ends with the
.so
extension. For example, the actual file name for the
log
library is liblog.so
.<ndk>/platforms/android-<sdk_version>/arch-<abi>/usr/lib/
.LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM
A static library, android_native_app_glue
, that the
application uses to manage NativeActivity
lifecycle events, along
with touch input.
LOCAL_STATIC_LIBRARIES := android_native_app_glue
The final line tells the build system to build this static library.
ndk-build
places the built library
(libandroid_native_app_glue.a
) into the obj
directory
generated during the build process. The next sample discusses the
android_native_app_glue in more detail.
$(call import-module,android/native_app_glue)
For more information about the Application.mk file, consult the Application.mk section of this guide.
Application.mk
This line defines the minimum level of Android API Level support.
APP_PLATFORM := android-10
Because there is no ABI definition, the build system defaults to building only for armeabi.
This file essentially contains the entire progam.
The following includes correspond to the libraries, both shared and static,
enumerated in Android.mk
.
#include <EGL/egl.h> #include <GLES/gl.h> #include <android/sensor.h> #include <android/log.h> #include <android_native_app_glue>
android_native_app_glue
calls the following function,
passing it a predefined state structure. It also serves as a wrapper that
simplifies handling of NativeActivity
callbacks.
void android_main(struct android_app* state) {
Next, the program handles events queued by the glue library. The event handler follows the state structure.
struct engine engine; // Make sure glue isn't stripped by suppressing link-time optimization that removes unreferenced code. app_dummy(); memset(&engine, 0, sizeof(engine)); state->userData = &engine; state->onAppCmd = engine_handle_cmd; state->onInputEvent = engine_handle_input; engine.app = state;
The application prepares to start monitoring the sensors, using the
APIs in sensor.h
.
engine.sensorManager = ASensorManager_getInstance(); engine.accelerometerSensor = ASensorManager_getDefaultSensor(engine.sensorManager, ASENSOR_TYPE_ACCELEROMETER); engine.sensorEventQueue = ASensorManager_createEventQueue(engine.sensorManager, state->looper, LOOPER_ID_USER, NULL, NULL);
Now, a loop begins, in which the application polls the system for
messages (sensor events). It sends messages to
android_native_app_glue
, which checks to see whether they match
any onAppCmd
events defined in android_main
. When a
match occurs, the message is sent to the handler for execution.
while (1) { // Read all pending events. int ident; int events; struct android_poll_source* source; // If not animating, we will block forever waiting for events. // If animating, we loop until all events are read, then continue // to draw the next frame of animation. while ((ident=ALooper_pollAll(engine.animating ? 0 : -1, NULL, &events, (void**)&source)) >= 0) { // Process this event. if (source != NULL) { source->process(state, source); } // If a sensor has data, process it now. if (ident == LOOPER_ID_USER) { if (engine.accelerometerSensor != NULL) { ASensorEvent event; while (ASensorEventQueue_getEvents(engine.sensorEventQueue, &event, 1) > 0) { LOGI("accelerometer: x=%f y=%f z=%f", event.acceleration.x, event.acceleration.y, event.acceleration.z); } } } // Check if we are exiting. if (state->destroyRequested != 0) { engine_term_display(&engine); return; } }
Once the queue is empty, and the program exits the polling loop, the program calls OpenGL to draw the screen.
if (engine.animating) { // Done with events; draw next animation frame. engine.state.angle += .01f; if (engine.state.angle > 1) { engine.state.angle = 0; } // Drawing is throttled to the screen update rate, so there // is no need to do timing here. engine_draw_frame(&engine); } }