Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Running Native C/C++ Code with JNI on the NDK

Author: Ian Darwin -- Published? true -- FormatLanguage: W

Problem:

You need to run parts of your application natively in order to use existing C/C++ code or, possibly, to improve performance of CPU-intensive code

Solution:

Use JNI (Java Native Interface) via the Android Native Development Kit.

Discussion:

Standard Java has always allowed you to load native or compiled code into your Java program, and Android's Dalvik runtime supports this in a way that is pretty much identical with the original. Why would you as a developer want to do such a thing? One reason might be to access OS-dependent functionality. Another is speed: native code will likely run faster than Java, at least at present, although there is some contention as to how much difference this really makes. Search the web for conflicting answers.

Note that if your *entire* application is in C/C++, for example a multi-platform game using OpenGL extensively from C/C++, you will find it easier to use the NativeActivity class. While this requires API level 9 (Android 2.2.x), it will really make your life simpler. See [1].

The native code language bindings are defined for code that has been written in C or C++. If you need to access a language other than C/C++, you could write a bit of C/C++ and have it pass control to other functions or applications, but you should also consider using the Android Scripting Environment.

For this example I use a simple numeric calculation, computing the square root of a double using the simple Newton-Raphson iterative method. The code provides both a Java and a C version, to compare the speeds.

Ian's Basic Steps: Java Calling Native Code

To call native code from Java:

  1. Install the Android Native Development Kit (NDK) in addition to the ADK.
  2. Write Java code that declares and calls a native method.
  3. Compile this Java code.
  4. Create a .h header file using javah.
  5. Write a C function that includes this header file and implements the native method to do the work.
  6. Prepare the Android.mk (and optionally Application.mk) configuration files.
  7. Compile the C code into a loadable object using $NDK/ndk-build.
  8. Package and deploy your application, and test it.

The preliminary step is to download the NDK as a tar or zip file, extract it someplace convenient, and set the environment variable such as NDK to where you've installed it, for referring back to the NDK install. You'll want this to read documentation as well as to run the tools.

The first step is to write Java code that declares and calls a native method. To declare the method, use the keyword native to indicate that the method is native. To use the native method, no special syntax is used, but your application - typically in your main Activity - must provide a static code block that loads your native method using System.loadLibrary() . (The dynamically loadable module will be created in Step 6.) Static blocks are executed when the class containing them is loaded; loading the native code here ensures it is in memory when needed!

Object variables that your native code may modify should carry the volatile modifier. In my example, SqrtDemo.java contains the native method declaration (as well as a Java implementation of the algorithm).

public class SqrtDemo {

	public static final double EPSILON = 0.05d;

	public static native double sqrtC(double d);

	public static double sqrtJava(double d) {
		double x0 = 10.0, x1 = d, diff;
		do {
			x1 = x0 - (((x0 * x0) - d) / (x0 * 2));
			diff = x1 - x0;
			x0 = x1;
		} while (Math.abs(diff) > EPSILON);
		return x1;
	}
}

The Activity class Main.java uses the native code:

// In the Activity class, outside any methods:
static {
	System.loadLibrary("sqrt-demo");
}

// In a method of the Activity class where you need to use it:
double d = SqrtDemo.sqrtC(123456789.0);

The next step is simple; just build the project normally, using the ADK Eclipse Plugin or Ant.

Next, you need to create a C-language .h header file that provides the interface between the JVM and your native code. Use javah to produce this file. javah needs to read the class that declares one or more native methods, and will generate a .h file specific to the package and class name.

mkdir jni // keep everything JNI-related here
javah -d jni -classpath bin foo.ndkdemo.SqrtDemo // produces foo_ndkdemo_SqrtDemo.h

The .h file produced is a "glue" file, not really meant for human consumption and particularly not for editing. But by inspecting the resulting .h file, you'll see that the C method's name is composed of the name Java, the package name, the class name, and the method name:

JNIEXPORT jdouble JNICALL Java_foo_ndkdemo_SqrtDemo_sqrtC
  (JNIEnv *, jclass, jdouble);

Then create a C function that does the work. You must import the .h file and use the same function signature as is used in the .h file.

This function can do whatever it wishes. Note that it is passed two arguments before any declared arguments: a JVM environment variable and a "this" handle for the invocation context object. The table shows the correspondence between Java types and the C types (JNI types) used in the C code.

Java and JNI types
Java type JNI Java array type JNI
byte jbyte byte[] jbyteArray
short jshort short[] jshortArray
int jint int[] jintArray
long jlong long[] jlongArray
float jfloat float[] jfloatArray
double jdouble double[] jdoubleArray
char jchar char[] jcharArray
boolean jboolean boolean[] jbooleanArray
void jvoid
Object jobject Object[] jobjectArray
Class jclass
String jstring
array jarray
Throwable jthrowable

The following is the complete C native implementation. It simply computes the square root of the input number, and returns that. The method is static, so there is no use made of the "this" pointer.

// jni/sqrt-demo.c

#include <stdlib.h>

#include "foo_ndkdemo_SqrtDemo.h"

JNIEXPORT jdouble JNICALL Java_foo_ndkdemo_SqrtDemo_sqrtC(
	JNIEnv *env, jclass clazz, jdouble d) {
	
	jdouble x0 = 10.0, x1 = d, diff;
	do {
		x1 = x0 - (((x0 * x0) - d) / (x0 * 2));
		diff = x1 - x0;
		x0 = x1;
	} while (labs(diff) > foo_ndkdemo_SqrtDemo_EPSILON);
	return x1;
}

The implementation is basically the same as the Java version. Note that javah even maps the final double EPSILON from the Java class SqrtDemo into a #define for use within the C version.

The next step is to prepare the file Android.mk, also in the jni folder. For a simple shared library, this example will suffice:

# Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := sqrt-demo
LOCAL_SRC_FILES := sqrt-demo.c

include $(BUILD_SHARED_LIBRARY)

Finally, you compile the C code into a loadable object. Under desktop Java, the details depend on platform, compiler, etc. However, the NDK provides a build script to automate this. Assuming you have set the NDK variable to the install root of the NDK download from Step 1, you only need to type

$ $NDK/ndk-build  # for Linux, Unix, OS-X?
> %NDK%/ndk-build # for MS-Windows

Compile thumb  : sqrt-demo <= sqrt-demo.c
SharedLibrary  : libsqrt-demo.so
Install        : libsqrt-demo.so => libs/armeabi/libsqrt-demo.so

And you're done! Just package and run the application normally. The full download example for this chapter includes buttons to run the sqrt function a number of times in either Java or C and compare the times. Note that at present it does this work on the event thread, so large numbers of repetitions will result in "Application Not Responding" errors, which will mess up the timing.

Congratulations! You've called a native method. Your code may run slightly faster. However, you will require extra work for portability; as Android begins to run on more hardware platforms, you will have to (at least) add them to the Application.mk file. If you have used any assembler code, the problem is much worse.

Beware that problems with your native code can and will crash the runtime process right out from underneath the Java Virtual Machine. The JVM can do nothing to protect itself from poorly written C/C++ code. Memory must be managed by the programmer; there is no automatic garbage collection of memory obtained by the system runtime allocator. You're dealing directly with the operating system and sometimes even the hardware, so, 'Be careful. Be very careful.'

See Also:

There is a recipe in Chapter 26 of the Java Cookbook which shows variables from the Java class being accessed from within the native code. The official documentation for Android's NDK is at The Android Native SDK information page. Considerable documentation is included in the docs folder of the NDK download. If you need more information on Java Native Methods, you might be interested in the comprehensive treatment found in Essential JNI: Java Native Interface by Rob Gordon (Prentice Hall), originally written for Desktop Java.

Download:

The source code for this project can be downloaded from http://projects.darwinsys.com/ndkdemo-src.zip.

Download:

The source code for this project is in the Android Cookbook repository at http://github.com/IanDarwin/Android-Cookbook-Examples, in the subdirectory NdkDemo.
No records found.