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)