The Android Native Development Kit (NDK) - Advanced Topics - Programming Android (2011)

Programming Android (2011)

Part IV. Advanced Topics

Chapter 18. The Android Native Development Kit (NDK)

Java Native Interface (JNI) is a part of the Java standard that enables developers to write native methods in other languages, such as C and C++, and call those methods from Java code. This is especially useful when you want to use platform-specific features or take advantage of hardware in the platform, such as achieving faster numerical computation by taking advantage of FPU instruction set extensions, or letting graphics-intensive code exploit the OpenGL API. This chapter covers JNI basics for programmers using the Android NDK. For further details, see the Java Native Interface Specification.

Typical good candidates for implementation in C or C++ using the NDK are self-contained, CPU-intensive operations that don’t allocate much memory, such as signal processing, physics simulation, and so on. Simply recoding a random method to run in C usually does not result in a large performance increase. When examining whether you should develop in native code, think about your requirements and see whether the Android SDK already provides the functionality you need. You’ll find that in most cases the Android SDK will fulfill your needs. It is worth remembering that, although most Android platforms are ARM right now, there are likely to be others (such as Atom) in the future. Using NDK makes your code nonportable.

JNI is what Android uses to access code implemented in C or C++, and the Android NDK is an optional extension to the Android SDK that supports the use of C or C++ code in Android applications. To make things as easy as possible for the Java developer, JNI lets a native method use Java objects in the same way that Java code uses these objects. Within the native method, Java objects can be created, inspected, and used to perform its tasks. This same ability to inspect and use Java objects enables the native method to use other Java objects passed to it from the Java application (and thus the Android application).

Native Methods and JNI Calls

JNI created certain conventions to allow calls to methods from other languages. The changes on the side of the native methods (essentially C or C++ libraries) are greater than the changes required on the Java side.

Conventions on the Native Method Side

When a virtual machine (VM)—in Android’s case, Dalvik—invokes a function implemented in C or C++, it passes two special parameters:

§ A JNIEnv pointer, which is a kind of handle for the thread in the VM that called the native method

§ A jobject pointer, which is a reference to the calling class

These parameters are passed transparently to Java code. That is, they do not appear in the method signature declared in the calling Java code. The Java call just explicitly passes any other parameters needed by the called function.

A JNI function may look like this:

/* sample method where the Java call passed no parameters */

void Java_ClassName_MethodName (JNIEnv *env, jobject obj) {

/* do something */

}

/* another sample method with two parameters passed, returning a double */

jdouble Java_ClassName_MethodName ( JNIEnv* env, jobject obj,

jdouble x, jdouble y) {

return x + y;

}

These examples show the two parameters passed automatically to every native method, and two parameters with types that map to Java types.

When a native method is called, it runs in the same process and the same thread as the Java code that calls it. As we will see later in this chapter, it can allocate memory from the Java heap to take advantage of garbage collection, or outside the Java heap to circumvent Java memory management. Variables allocated on the stack in C or C++ code have the same semantics as in native executables in those languages. They are allocated from the stack space for the process they run in.

JNI provides types that correspond to Java types, as shown in Table 18-1.

Table 18-1. Data mapping

Native type

Java type

Description

boolean

jboolean

Unsigned 8 bits

byte

jbyte

Signed 8 bits

char

jchar

Unsigned 16 bits

short

jshort

Signed 16 bits

int

jint

Signed 32 bits

long

jlong

Signed 64 bits

float

jfloat

32 bits

double

jdouble

64 bits

void

void

N/A

In compound types such as objects, arrays, and strings, the native code must explicitly convert the data by calling conversion methods, which are accessible through the JNIEnv pointer.

Conventions on the Java Side

Before native methods can be used within a Java class, the library containing native methods must be loaded by calling System.loadLibrary. Typically, the class that needs the native method would statically load this. Native methods accessed by a class are declared in the class using the nativekeyword:

public class ClassWithNativeMethod {

public native double nativeMethod(); // native method

static {

System.loadLibrary("sample"); // load lib called 'sample'

}

public static void main(String[] args) {

ClassWithNativeMethod cwnm = new ClassWithNativeMethod();

double answer = cwnm.nativeMethod(); // call native method

System.out.println("Answer is : "+answer);

}

}

The Android NDK

The Android Native Development Kit (NDK) is a companion tool to the Android SDK. If you use the NDK to create native code, your applications are still packaged into an .apk file and run inside a VM on the device. The fundamental Android application model does not change.

Setting Up the NDK Environment

In order to use the NDK, you must first install and set up the SDK. The system requirements for installing and using the NDK are as follows:

§ Windows XP (32-bit) or Vista (32- or 64-bit) with Cygwin 1.7 or later

§ Mac OS X 10.4.8 or later

§ Linux (32- or 64-bit)

Necessary development tools that are not provided in the NDK are as follows:

§ GNU Make 3.81 or later

§ A recent version of Awk (either GNU Awk or Nawk)

First, download and install the NDK (http://developer.android.com/sdk/ndk/index.html). Installation is simple: unzip the NDK into any directory. Here, we will refer to that directory as ndk.

Once the NDK is downloaded and installed, you will find quite a bit of documentation (located in the ndk/docs directory). We highly recommend that you read the documentation, starting with OVERVIEW.html. Also included in the NDK are samples (located in ndk/samples). The samples cover quite a bit more than this chapter will, so after you have used the NDK a bit, we recommend that you go through the samples.

Compiling with the NDK

In order to develop native code with the NDK, you will need to do the following:

1. Create a jni directory within your project.

2. Place your native source in the jni directory.

3. Create an Android.mk file (and, optionally, an Application.mk file) in the jni directory.

4. Run the ndk/ndk-build command from within the jni directory.

The optional Application.mk file describes what native modules are needed for your application as well as specific ABI types to build against. For more details, check APPLICATION-MK.html in the documentation. A sample Application.mk file follows:

# Build both ARMv5TE and ARMv7-A machine code.

APP_ABI := armeabi armeabi-v7a

# What platform to build against (android-3 (1.5) - android-9 (2.3))

APP_PLATFORM := android-9

The Android.mk file describes your source to the build system. It is really a tiny GNU Makefile fragment that is parsed by the build system when building your app. For more details, read the ANDROID-MK.html documentation file. A sample Android.mk follows:

# Must define the LOCAL_PATH and return the current dir

LOCAL_PATH := $(call my-dir)

# Cleans various variables... making a clean build

include $(CLEAR_VARS)

# Identify the module/library's name

LOCAL_MODULE := sample

# Specify the source files

LOCAL_SRC_FILES := sample.c

# Load local libraries (here we load the log library)

LOCAL_LDLIBS := -llog

# Build the shared library defined above

include $(BUILD_SHARED_LIBRARY)

Once you have written Android.mk, and optionally Application.mk and the native source files themselves, run ndk/ndk-build within the project directory to build your libraries. Should the build be successful, the shared libraries will be copied into your application’s root project directory and added to its build.

JNI, NDK, and SDK: A Sample App

To help you understand how the SDK and native source can be put together, we provide the following sample app. It describes an activity called SampleActivityWithNativeMethods. The Android manifest fragment is:

<activity android:name=".SampleActivityWithNativeMethods"

android:label="Sample Activity With Native Methods"

android:debuggable="true" />

The SampleActivityWithNativeMethods activity uses the following layout:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<Button

android:id="@+id/whatami"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:paddingTop="5dp"

android:paddingBottom="5dp"

android:text="What CPU am I?"

/>

</LinearLayout>

The sample C library source has a method called whatAmI, which our Java activity will hook to the button with the whatami ID. We also define a function named LOGINFO, resolving to an __android_log_print call. This is how the Android log is written:

// the jni library MUST be included

#include <jni.h>

// the log lib is included

#include <android/log.h>

// usage of log

#define LOGINFO(x...) __android_log_print(ANDROID_LOG_INFO,"SampleJNI",x)

jstring Java_com_oreilly_demo_pa_ch18_SampleActivityWithNativeMethods_whatAmI(

JNIEnv* env,jobject thisobject) {

LOGINFO("SampleJNI","Sample Info Log Output");

return (*env)->NewStringUTF(env, "Unknown");

}

Our Android.mk file follows. Note that it causes the log library to be loaded:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := sample

LOCAL_SRC_FILES := sample.c

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

Finally, here is the SampleActivityWithNativeMethods Java activity’s source code. The class loads the sample library and declares the whatAmI() native method. When the button is clicked, the whatAmI() method is called and returns "Unknown". This then shows a Toast with the string "CPU: Unknown". If you find the output uninformative, rest assured that we will include the CPU information in a later section:

package com.oreilly.pa.ch18;

import com.oreilly.pa.ch18.R;

import android.widget.Toast;

public class SampleActivityWithNativeMethods extends Activity {

static {

System.loadLibrary("sample"); // load our sample lib

}

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.sample);

setupview();

}

public native String whatAmI(); // sample lib native method

private void setupview() {

findViewById(R.id.whatami).setOnClickListener(

new View.OnClickListener() {

public void onClick(View v) {

String whatami = whatAmI();

Toast.makeText(getBaseContext(), "CPU: "+whatami,

Toast.LENGTH_SHORT).show();

}

});

}

Android-Provided Native Libraries

The NDK comes with the following set of headers for stable native APIs:

§ libc (C library) headers

§ libm (math library) headers

§ JNI interface headers

§ libz (Zlib compression) headers

§ liblog (Android logging) header

§ OpenGL ES 1.1 and OpenGL ES 2.0 (3D graphics libraries) headers

§ libjnigraphics (pixel buffer access) header (for Android 2.2 and later)

§ A minimal set of headers for C++ support

§ OpenSL ES native audio libraries

§ Android native application APIs

NOTE

Except for the libraries just listed, native system libraries in the Android platform are not stable and may change in future platform versions. Your applications should use only the stable native system libraries provided in the NDK.

Some libraries, such as libc and libm, are automatically referenced in the build and thus need to be referenced only in the source code as includes. However, some libraries are not automatically referenced, and thus require specific statements within the Android.mk build file.

Here is a sample Android.mk file that imports the cpufeatures module, which will give us the information missing from our earlier whatAmI example:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := sample

LOCAL_SRC_FILES := sample.c

LOCAL_LDLIBS := -llog

# Here we reference the cpufeatures module

LOCAL_STATIC_LIBRARIES := cpufeatures

include $(BUILD_SHARED_LIBRARY)

# Here we import the cpufeatures modules

$(call import-module,cpufeatures)

The following source (extending the whatAmI function we showed in the previous section) utilizes the cpufeatures module we have included:

// Include the cpu-features module

#include <cpu-features.h>

#include <jni.h>

#include <android/log.h>

#define LOGINFO(x...) __android_log_print(ANDROID_LOG_INFO,"SampleJNI",x)

jstring Java_com_oreilly_demo_pa_ch18_SampleActivityWithNativeMethods_whatAmI(

JNIEnv* env, jobject thisobject) {

LOGINFO("SampleJNI","Sample Info Log Output");

// -- Here we use the cpufeatures -- //

uint64_t cpu_features;

if (android_getCpuFamily() != ANDROID_CPU_FAMILY_ARM) {

return (*env)->NewStringUTF(env, "Not ARM");

}

cpu_features = android_getCpuFeatures();

if ((cpu_features & ANDROID_CPU_ARM_FEATURE_ARMv7) != 0) {

return (*env)->NewStringUTF(env, "ARMv7");

} else if ((cpu_features & ANDROID_CPU_ARM_FEATURE_VFPv3) != 0) {

return (*env)->NewStringUTF(env, "ARM w VFPv3 support");

} else if ((cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) != 0) {

return (*env)->NewStringUTF(env, "ARM w NEON support");

}

// -- End cpufeatures usage -- //

return (*env)->NewStringUTF(env, "Unknown");

}

Building Your Own Custom Library Modules

This section puts together several techniques shown throughout the chapter to create and use a simple C module that uses the math library to calculate a power. We’ll start with the Android.mk file. Notice that we need to build the library (sample_lib) and export the includes. This library is then referenced in the sample:

LOCAL_PATH := $(call my-dir)

# this is our sample library

include $(CLEAR_VARS)

LOCAL_MODULE := sample_lib

LOCAL_SRC_FILES := samplelib/sample_lib.c

# we need to make sure everything knows where everything is

LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/samplelib

include $(BUILD_STATIC_LIBRARY)

# sample uses the sample lib we created

include $(CLEAR_VARS)

LOCAL_MODULE := sample

LOCAL_SRC_FILES := sample.c

LOCAL_LDLIBS := -llog

# We load our sample lib

LOCAL_STATIC_LIBRARIES := sample_lib

include $(BUILD_SHARED_LIBRARY)

We have a short header file, sample_lib.h:

#ifndef SAMPLE_LIB_H

#define SAMPLE_LIB_H

extern double calculatePower(double x, double y);

#endif

The source code for our function, sample_lib.c, is:

#include "sample_lib.h"

// we include the math lib

#include "math.h"

// we use the math lib

double calculatePower(double x, double y) {

return pow(x, y);

}

Following is the sample.c file that glues our sample_lib library to the Java code:

// we include the sample_lib

#include "sample_lib.h"

#include <jni.h>

#include <android/log.h>

#define LOGINFO(x...) __android_log_print(ANDROID_LOG_INFO,"SampleJNI",x)

jdouble

Java_com_oreilly_demo_pa_ch18_SampleActivityWithNativeMethods_calculatePower(

JNIEnv* env, jobject thisobject, jdouble x, jdouble y) {

LOGINFO("Sample Info Log Output");

// we call sample-lib's calculate method

return calculatePower(x, y);

}

The layout the Activity will use is:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

>

<EditText

android:id="@+id/x"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:paddingTop="5dp"

android:paddingBottom="5dp"

android:textColor="#000"

android:hint="X Value"

/>

<EditText

android:id="@+id/y"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:paddingTop="5dp"

android:paddingBottom="5dp"

android:textColor="#000"

android:hint="Y Value"

/>

<Button

android:id="@+id/calculate"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:paddingTop="5dp"

android:paddingBottom="5dp"

android:text="Calculate X^Y"

/>

</LinearLayout>

Following is the SampleActivityWithNativeMethods activity that we have modified to use with this new library. The sample library is loaded and the calculatePower() method is declared. When the “calculate” button is clicked, we then take the numbers provided from the two edit text boxes (using a default of 2 if the text is missing or is not a number) and pass them to the calculatePower() method. The returned double is then popped up as part of a Toast:

package com.oreilly.pa.ch18;

import com.oreilly.pa.ch18.R;

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.widget.EditText;

import android.widget.Toast;

public class SampleActivityWithNativeMethods extends Activity {

static {

System.loadLibrary("sample"); // load our sample lib

}

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.sample);

setupview();

}

// sample lib native method

public native double calculatePower(double x, double y);

private void setupview() {

findViewById(R.id.calculate).setOnClickListener(

new View.OnClickListener() {

public void onClick(View v) {

String answer = "";

double x = 2;

double y = 2;

String sx = ((EditText) findViewById(R.id.x)).getText().toString();

String sy = ((EditText) findViewById(R.id.y)).getText().toString();

if(sx == null) {

answer = "X defaults to 2\n";

} else {

try {

x = Double.parseDouble(sx);

} catch (Exception e) {

answer = "X is not a number, defaulting to 2\n";

x = 2;

}

}

if(sy == null) {

answer += "Y defaults to 2\n";

} else {

try {

y = Double.parseDouble(sy);

} catch (Exception e) {

answer = "Y is not a number, defaulting to 2\n";

y = 2;

}

}

double z = calculatePower(x, y);

answer += x+"^"+y+" = "+z;

Toast.makeText(SampleActivityWithNativeMethods.this, answer,

Toast.LENGTH_SHORT).show();

}

});

}

}

Native Activities

Android 2.3 (API level 9) and Android NDK revision 5 let you write entire activities and applications as native source by using the NativeActivity class to access the Android application life cycle.

To utilize this method, the android.app.NativeActivity needs to be referenced in the Android manifest file. Note that the application reference has a hasCode attribute. This attribute should be set to false if there is no Java in the application (only the NativeActivity). In this case, however, since we do have Java code, we set the value to true:

<!-- This .apk has Java code, so set hasCode to true which is the default. -->

<!-- if this only had a native app (only the activity

called 'android.app.NativeActivity') -->

<!-- then set to false -->

<application android:icon="@drawable/icon" android:label="@string/app_name"

android:hasCode="true" >

<activity android:name=".NDKApp" android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name="android.app.NativeActivity"

android:label="SampleNativeActivity"

android:debuggable="true" >

<!-- here we declare what lib to reference -->

<meta-data android:name="android.app.lib_name"

android:value="sample_native_activity" />

</activity>

</application>

In this example, we use the android_native_app_glue.h header file instead of native_activity.h. The native_activity.h interface is based on a set of application-provided callbacks that will be called by the Activity’s main thread when certain events occur. This means callbacks should not block, and thereby is constraining. The android_native_app_glue.h file exposes a helper library with a different execution model that provides a means for the application to implement its own main function in a different thread. The function must be named android_main(), and is called when the application is created and an android_app object is passed to it. This provides a means to reference the application or activity and listen in on various life cycle events.

The following simple nativeactivity example constructs an Activity and listens in on Motion events. The Motion events’ x and y screen coordinates are then sent to the LogCat:

#include <jni.h>

#include <android/log.h>

#include <android_native_app_glue.h>

// usage of log

#define LOGINFO(x...) __android_log_print(ANDROID_LOG_INFO,"SampleNativeActivity",x)

// handle commands

static void custom_handle_cmd(struct android_app* app, int32_t cmd) {

switch(cmd) {

case APP_CMD_INIT_WINDOW:

LOGINFO("App Init Window");

break;

}

}

// handle input

static int32_t custom_handle_input(struct android_app* app, AInputEvent* event) {

// we see a motion event and we log it

if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {

LOGINFO("Motion Event: x %f / y %f", AMotionEvent_getX(event, 0),

AMotionEvent_getY(event, 0));

return 1;

}

return 0;

}

// This is the function that application code must implement,

// representing the main entry to the app.

void android_main(struct android_app* state) {

// Make sure glue isn't stripped.

app_dummy();

int events;

// set up so when commands happen we call our custom handler

state->onAppCmd = custom_handle_cmd;

// set up so when input happens we call our custom handler

state->onInputEvent = custom_handle_input;

while (1) {

struct android_poll_source* source;

// we block for events

while (ALooper_pollAll(-1, NULL, &events, (void**)&source) >= 0) {

// Process this event.

if (source != NULL) {

source->process(state, source);

}

// Check if we are exiting.

if (state->destroyRequested != 0) {

LOGINFO("We are exiting");

return;

}

}

}

}

This is the Android.mk file for the sample nativeactivity. Note that it loads and refers to the android_native_app_glue module:

LOCAL_PATH := $(call my-dir)

# this is our sample native activity

include $(CLEAR_VARS)

LOCAL_MODULE := sample_native_activity

LOCAL_SRC_FILES := sample_nativeactivity.c

LOCAL_LDLIBS := -llog -landroid

LOCAL_STATIC_LIBRARIES := android_native_app_glue

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)

Following is the main Java Android activity that is called when the user launches the application. Clicking on the button launches the NativeActivity that we have provided:

package com.oreilly.pa.ch18;

import com.oreilly.pa.ch18.R;

import android.app.Activity;

import android.content.Intent;

import android.os.Bundle;

import android.view.View;

public class NDKApp extends Activity {

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

findViewById(R.id.nativeactivity).setOnClickListener(

new View.OnClickListener() {

public void onClick(View v) {

startActivity(new Intent(getBaseContext(),

android.app.NativeActivity.class)); // call nativeactivity

}

});

}

}

If you compile and run this example, you will note that when the native activity is launched, the screen is blank, and if you are viewing the LogCat you will see various log messages appear (especially when moving your finger across the screen). This, however, is not much fun. So to spruce things up, we wish to do something with the screen. The following example adds the use of OpenGL ES to change the screen’s color.

Here is the native source with the additional OpenGL ES material. It simply turns the screen bright red when the activity is displayed:

#include <jni.h>

#include <android/log.h>

#include <android_native_app_glue.h>

#include <EGL/egl.h>

#include <GLES/gl.h>

// usage of log

#define LOGINFO(x...)

__android_log_print(ANDROID_LOG_INFO,"NativeWOpenGL",x)

struct eglengine {

EGLDisplay display;

EGLSurface surface;

EGLContext context;

};

// initialize the egl engine

static int engine_init_display(struct android_app* app, struct eglengine* engine) {

const EGLint attribs[] = {

EGL_SURFACE_TYPE, EGL_WINDOW_BIT,

EGL_BLUE_SIZE, 8,

EGL_GREEN_SIZE, 8,

EGL_RED_SIZE, 8,

EGL_NONE

};

EGLint w, h, dummy, format;

EGLint numConfigs;

EGLConfig config;

EGLSurface surface;

EGLContext context;

EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

eglInitialize(display, 0, 0);

eglChooseConfig(display, attribs, &config, 1, &numConfigs);

eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);

ANativeWindow_setBuffersGeometry(app->window, 0, 0, format);

surface = eglCreateWindowSurface(display, config, app->window, NULL);

context = eglCreateContext(display, config, NULL, NULL);

if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE) {

LOGINFO("eglMakeCurrent FAIL");

return -1;

}

eglQuerySurface(display, surface, EGL_WIDTH, &w);

eglQuerySurface(display, surface, EGL_HEIGHT, &h);

engine->display = display;

engine->context = context;

engine->surface = surface;

glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

glEnable(GL_CULL_FACE);

glShadeModel(GL_SMOOTH);

glDisable(GL_DEPTH_TEST);

return 0;

}

// draw to the screen

static void engine_color_screen(struct eglengine* engine) {

if (engine->display == NULL) {

return;

}

glClearColor(255, 0, 0, 1); // let's make the screen all red

glClear(GL_COLOR_BUFFER_BIT);

eglSwapBuffers(engine->display, engine->surface);

}

// when things need to be terminated

static void engine_terminate(struct eglengine* engine) {

if (engine->display != EGL_NO_DISPLAY) {

eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE,

EGL_NO_CONTEXT);

if (engine->context != EGL_NO_CONTEXT) {

eglDestroyContext(engine->display, engine->context);

}

if (engine->surface != EGL_NO_SURFACE) {

eglDestroySurface(engine->display, engine->surface);

}

eglTerminate(engine->display);

}

engine->display = EGL_NO_DISPLAY;

engine->context = EGL_NO_CONTEXT;

engine->surface = EGL_NO_SURFACE;

}

// handle commands

static void custom_handle_cmd(struct android_app* app, int32_t cmd) {

struct eglengine* engine = (struct eglengine*)app->userData;

switch(cmd) {

// things are starting up... let's initialize the engine and color the screen

case APP_CMD_INIT_WINDOW:

if (app->window != NULL) {

engine_init_display(app, engine);

engine_color_screen(engine);

}

break;

case APP_CMD_TERM_WINDOW: // things are ending...let's clean up the engine

engine_terminate(engine);

break;

}

}

// handle input

static int32_t custom_handle_input(struct android_app* app, AInputEvent* event) {

// we see a motion event and we log it

if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) {

LOGINFO("Motion Event: x %f / y %f", AMotionEvent_getX(event, 0),

AMotionEvent_getY(event, 0));

return 1;

}

return 0;

}

// This is the function that application code must implement,

// representing the main entry to the app.

void android_main(struct android_app* state) {

// Make sure glue isn't stripped.

app_dummy();

// here we add the eglengine to the app

struct eglengine engine;

memset(&engine, 0, sizeof(engine));

// set engine as userdata so we can reference

state->userData = &engine;

int events;

// set up so when commands happen we call our custom handler

state->onAppCmd = custom_handle_cmd;

// set up so when input happens we call our custom handler

state->onInputEvent = custom_handle_input;

while (1) {

struct android_poll_source* source;

// we block for events

while (ALooper_pollAll(-1, NULL, &events, (void**)&source) >= 0) {

// Process this event.

if (source != NULL) {

source->process(state, source);

}

// Check if we are exiting.

if (state->destroyRequested != 0) {

LOGINFO("We are exiting");

return;

}

}

}

}

The Android.mk file for the sample_native_activity_opengl activity loads the EGL and GLESv1_CM libraries:

LOCAL_PATH := $(call my-dir)

# this is our sample native activity with opengl

include $(CLEAR_VARS)

LOCAL_MODULE := sample_native_activity_opengl

LOCAL_SRC_FILES := sample_nativeactivity_opengl.c

# loading the log , android, egl, gles libraries

LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv1_CM

LOCAL_STATIC_LIBRARIES := android_native_app_glue

include $(BUILD_SHARED_LIBRARY)

$(call import-module,android/native_app_glue)