NDK Programmer's Guide
native-activity

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.

AndroidManifest.xml

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" />

Android.mk

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.

  • log is a logging library.
  • android encompasses the standard Android support APIs for NDK. The Stable APIs section discusses these in more detail.
  • EGL, standardized by Khronos, corresponds to the platform-specific portion of the graphics API.
  • OpenGL ES, the version of OpenGL for Android, depends on EGL.

Note that, for each library:

  • The actual file name starts with lib, and ends with the .so extension. For example, the actual file name for the log library is liblog.so.
  • The library lives in the following directory, relative to the NDK root: <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.

main.c

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);
    }
}