Native code tips

From Android Wiki

Jump to: navigation, searcha

Android apps do not have to be written entirely in Java. It is possible to include C/C++ code and compile it with the Native Development Kit (NDK). This code then interfaces with the Java code through the Java Native Interface (JNI), of which the Dalvik virtual machine includes a (mostly) complete implementation.

When you might want to use the NDK:

  • Port C/C++ code from another platform.
  • Java code is too slow—but beware: “premature optimization is the root of all evil” (variously attributed to Donald Knuth or Tony Hoare). Do actual testing to determine where the slow parts of your code are, before trying to speed them up: the performance bottlenecks are often not where you think they are.
  • Allocate more memory than the 20MB or so permitted for your Java heap.

Reasons not to use the NDK:

  • Your compiled code ends up being architecture-specific. Sure, you can include alternative versions for, say, both ARM and x86 to cover the likely bases for now, but what happens if in the future some cheap Android devices out of China start using MIPS chips?
  • Debugging is hard. Instead of seeing a nice Java exception traceback in the logcat, pointing to the exact source line where the error occurred, you get a crash register dump. However, it is possible to narrow down the point of the error through the standard expedient of outputting debug messages at strategic points in the code (see Logging, below).

Better JNI Glue

The standard Android-provided jni.h makes it slightly awkward to call JNI routines. You can improve this by processing jni.h through JNIGlue, which generates more convenient static inline wrapper routines. For example, instead of

   const jclass SystemClass = (**env).FindClass(env, "java/lang/System");

you can do

   const jclass SystemClass = JNFindClass(env, "java/lang/System");

Logging

In Java code, you can write messages to the logcat using either the Log class, or simply write to the standard Java System.err diagnostic stream or System.out output stream. In the first case, you can set the logging level and the “tag” string used to prefix your messages; in the second case, the level is always warning (W), and the tag is always “System.err”, and in the last case, the level is info (I) and the tag is “System.out”.

In native code, the usual stdio streams of stdout and stderr are defined, but do not work—certainly trying to write to stdout or stderr not only does not display messages anywhere, it can lead to strange crashes elsewhere in your code.

Instead, you can use the native logging library. There is no public documentation for this, but the routines are in the android/log.h header file that you can include in your C/C++ code. Also in your Android.mk for building the native code, add -llog to the definition of your LOCAL_LDLIBS variable.

Messages written to the logcat in this way look like

   level/tag(pid): text

e.g.

   W/System.err( 1288): test app successfully started

The log levels as enumerated in android/log.h, and the corresponding single-letter codes that appear in the messages, are:

code char
ANDROID_LOG_UNKNOWN message does
not appear
ANDROID_LOG_DEFAULT
ANDROID_LOG_VERBOSE V
ANDROID_LOG_DEBUG D
ANDROID_LOG_INFO I
ANDROID_LOG_WARN W
ANDROID_LOG_ERROR E
ANDROID_LOG_FATAL F
ANDROID_LOG_SILENT S

GCC Extensions

The C and C++ compilers included with the NDK are built from GCC, so you have access to the usual GCC capabilities. For example, you can do the C99 #include <stdbool.h> and use the bool type, with values true and false, in conditional expressions in your code. You can enable more general C99 capabilities by putting a line like this in your Android.mk:

   LOCAL_CFLAGS += -std=c99

Personal tools