Introduction to
the Android NDK

Sebastian Mauer
GDG Aachen

CodeFest Karlsruhe
February 20th, 2014
Who am I?
Sebastian Mauer

GDG Aachen Co-Lead
Software Engineer

CS Student
I don’t work for Google…yet
Part I:
NDK? What is that?
Android Platforms

MIPS

ARM

x86
Two kinds of Apps

Apps in the DalvikVM
(that’s the kind of apps you probably know)

Native Apps
(created using the NDK)
One VM to rule them all
•

Dalvik is a Virtual Machine (VM)

•

A VM is a common abstraction across different
hardware platforms

•

„Translates“ VM Bytecode to platform specific
instructions
It just works™
•

The DalvikVM is already optimized for the x86
Platform

•

Apps relying on the Android SDK / Dalvik Bytecode

will automatically benefit from Platform-specific
Optimizations (like SSE & Co.)
From Source to Bytecode
Java
Sourcecode
Java
Bytecode (.class)
Dalvik
Bytecode (.dex)

JAR Archive

Dalvik
VM

Java
VM
Android VM
Dalvik VM

App

App

App

App

App

App

Linux Kernel
Hardware
ONE DOES NOT SIMPLY

RUN CODE ON MULTIPLE
PLATFORMS
NOT SURE IF NDK CAN HELP ME
RUN CODE FASTER

OR MAKE THINGS EVEN MORE
COMPLICATED
Part II:
Going native. The NDK.
What’s the NDK?
•

NDK stands for Native Development Kit

•

Allows to compile C/C++ code to native (read:
platform specific) executables/libraries.

•

Build scripts/toolkit to incorporate native code in
Android apps via the Java Native Interface (JNI)

•

Has to be compiled for every platform you want to
support
But why?
•

Performance

e.g., complex algorithms, multimedia applications, games

•

Differentiation 

app that takes advantage of direct CPU/HW access

e.g., using SSSE3 for optimization

•

Fluid and lag-free animations

•

Software code reuse
Why not?
What could possibly go wrong?
•

Performance improvements are not guaranteed

•

In fact, you could make it worse (read: slower).

•

Added complexity (Java/C++ Interop, Multiple
platforms)

•

Somewhat harder to debug
What’s an NDK app?
It’s an Android application that uses
native libraries.
!

Libraries are .so files, usually found
inside libs/CPU_ABI/.
!

These libs can be generated from
native sources inside jni folder,
game engines, or required by other
3rd party libraries.
!

There is no 100% native application.
Even an application purely written
in C/C++, using native_app_glue.h,
will be executed in the context of
the Dalvik Virtual Machine.
NDK Development in a Nutshell
C/C++Code

Makefile

ndk-build

APP_ABI := all
or APP_ABI := x86

SDK APIs

Java* calls

GDB debug

through jni

Android* Applications

Java Framework

Java Application

JNI
Native Libs
Bionic C Library

NDK APIs
NDK Anatomy
Dalvik VM

Dalvik Bytecode
Java Native
Interface
(JNI)

NDK compiled binary
Native Platform
Compatibility with Standard C/C++
•

Bionic C Library:

Lighter than standard GNU C Library

Not POSIX compliant

pthread support included, but limited

No System-V IPCs

Access to Android* system properties

•

Bionic is not binary-compatible with the standard C library

•

It means you generally need to (re)compile everything
using the Android NDK toolchain
Pick One
•

By default, libstdc++ is used. It lacks: 

Standard C++ Library support (except some headers)

C++ exceptions support

RTTI support

•

Fortunately, you have other libs available with the NDK:
Runtime

Exceptions

RTTI

STL

system

No

No

No

gabi++

Yes

Yes

No

stlport

Yes

Yes

Yes

gnustl

Yes

Yes

Yes
Compile for all the platforms.
If you have the source code of your native libraries, you can compile it for several CPU
architectures by setting APP_ABI to all in the Makefile “jni/Application.mk”:!

APP_ABI=all

Put APP_ABI=all inside
Application.mk
Run ndk-build…
ARM v7a libs are built
ARM v5 libs are built
x86 libs are built
mips libs are built

The NDK will generate optimized code for all target ABIs
You can also pass APP_ABI variable directly to ndk-build, and specify each ABI:

ndk-build APP_ABI=x86
NDK
NativeActivity
• Only native code in the project
• android_main() entry point running in its
own thread
• Event loop to get input data and frame
drawing messages
/**
* This is the main entry point of a native application that is using
* android_native_app_glue. It runs in its own thread, with its own
* event loop for receiving input events and doing other things.
*/
void android_main(struct android_app* state);
Add native methods in Java
Declare native methods in your Java classes using the ‘native’ keyword:

public native String stringFromJNI();
!

Provide a native shared library built with the NDK that contains the
methods used by your application:

!

libMyLib.so

Your application must load the shared library (before use… during class
load for example):

static {
System.loadLibrary("MyLib");
}
Variant I: Use „javah“
javah creates JNI header stubs (.h) based on the Java source files from the
compiled Java classes files
!

Example:
> javah –d jni –classpath bin/classes 
com.example.hellojni.HelloJni
!

Generates com_example_hellojni_HelloJni.h file with the following
method definition:
JNIEXPORT jstring JNICALL
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv
*, jobject);
Variant I: Use „javah“
...
{
...
tv.setText( stringFromJNI() );
...
}

!

public native String

stringFromJNI();

static {
System.loadLibrary("hello-jni");
}

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env,
jobject thiz )
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
Variant II: JNI_OnLoad
• Proven method
• No more surprises after methods registration
• Less error prone when refactoring
• Add/remove native functions easily
• No symbol table issue when mixing C/C++ code
• Best spot to cache Java class object references
Variant II: JNI_OnLoad
In your library name your functions as you wish and declare the mapping with JVM methods:
jstring stringFromJNI(JNIEnv* env, jobject thiz)
{
return env->NewStringUTF("Hello from JNI !");
}

!

static JNINativeMethod exposedMethods[] = {
{"stringFromJNI","()Ljava/lang/String;",(void*)stringFromJNI},
}

!

()Ljava/lang/String; is the JNI signature of the Java* method, you can retrieve it using the
javap utility:
> javap -s -classpath binclasses -p com.example.hellojni.HelloJni
Compiled from "HelloJni.java“
…
public native java.lang.String stringFromJNI();
Signature: ()Ljava/lang/String;
…
Variant II: JNI_OnLoad
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)
!= JNI_OK)
return JNI_ERR;

!

jclass clazz = env->FindClass("com/example/hellojni/HelloJni");
if(clazz==NULL)
return JNI_ERR;
env->RegisterNatives(clazz, exposedMethods,
sizeof(exposedMethods)/sizeof(JNINativeMethod));
env->DeleteLocalRef(clazz);

}

return JNI_VERSION_1_6;
JNI Primitives
Java Type

Native 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
Memory Handling of Java Objects
Memory handling of Java* objects is done by the JVM:
!

• You only deal with references to these objects
• Each time you get a reference, you must not forget to
delete it after use
• local references are automatically deleted when the
native call returns to Java
• References are local by default
• Global references are only created by NewGlobalRef()
Creating a Java String
C:
jstring string =
(*env)->NewStringUTF(env, "new Java String");
!

C++:
jstring string = env->NewStringUTF("new Java String");

Memory is handled by the JVM, jstring is always a
reference. You can call DeleteLocalRef() on it once
you finished with it.
Convert a jstring into a C/C++ String
const char *nativeString = (*env)>GetStringUTFChars(javaString, null);
…
(*env)->ReleaseStringUTFChars(env, javaString, nativeString);
Call a Java Method
On an object instance:
jclass clazz = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, clazz, "methodName",
"(…)…");
if (mid != NULL)
(*env)->Call<Type>Method(env, obj, mid, parameters…);
!
Static call:
jclass clazz = (*env)->FindClass(env, "java/lang/String");
jmethodID mid = (*env)->GetStaticMethodID(env, clazz,
"methodName", "(…)…");
if (mid != NULL)
(*env)->CallStatic<Type>Method(env, clazz, mid,
parameters…);
!
• (…)…: method signature
• parameters: list of parameters expected by the Java* method
• <Type>: Java method return type
Handling Java Exceptions
// call to java methods may throw Java exceptions
jthrowable ex = (*env)->ExceptionOccurred(env);
if (ex!=NULL) {
(*env)->ExceptionClear(env);
// deal with exception
}
!

(*env)->DeleteLocalRef(env, ex);
Throw a Java Exception
jclass clazz =
(*env->FindClass(env, "java/lang/Exception");
!

if (clazz!=NULL)
(*env)->ThrowNew(env, clazz, "Message");
The exception will be thrown only when the JNI call returns to Java*, it
will not break the current native code execution.
Demo
Q&A
Thanks for your attention.

Introduction to the Android NDK

  • 1.
    Introduction to the AndroidNDK Sebastian Mauer GDG Aachen CodeFest Karlsruhe February 20th, 2014
  • 2.
    Who am I? SebastianMauer GDG Aachen Co-Lead Software Engineer CS Student I don’t work for Google…yet
  • 3.
  • 4.
  • 5.
    Two kinds ofApps Apps in the DalvikVM (that’s the kind of apps you probably know) Native Apps (created using the NDK)
  • 6.
    One VM torule them all • Dalvik is a Virtual Machine (VM) • A VM is a common abstraction across different hardware platforms • „Translates“ VM Bytecode to platform specific instructions
  • 7.
    It just works™ • TheDalvikVM is already optimized for the x86 Platform • Apps relying on the Android SDK / Dalvik Bytecode
 will automatically benefit from Platform-specific Optimizations (like SSE & Co.)
  • 8.
    From Source toBytecode Java Sourcecode Java Bytecode (.class) Dalvik Bytecode (.dex) JAR Archive Dalvik VM Java VM
  • 9.
  • 10.
    ONE DOES NOTSIMPLY RUN CODE ON MULTIPLE PLATFORMS
  • 11.
    NOT SURE IFNDK CAN HELP ME RUN CODE FASTER OR MAKE THINGS EVEN MORE COMPLICATED
  • 12.
  • 13.
    What’s the NDK? • NDKstands for Native Development Kit • Allows to compile C/C++ code to native (read: platform specific) executables/libraries. • Build scripts/toolkit to incorporate native code in Android apps via the Java Native Interface (JNI) • Has to be compiled for every platform you want to support
  • 14.
    But why? • Performance
 e.g., complexalgorithms, multimedia applications, games • Differentiation 
 app that takes advantage of direct CPU/HW access
 e.g., using SSSE3 for optimization • Fluid and lag-free animations • Software code reuse
  • 15.
  • 17.
    What could possiblygo wrong? • Performance improvements are not guaranteed • In fact, you could make it worse (read: slower). • Added complexity (Java/C++ Interop, Multiple platforms) • Somewhat harder to debug
  • 19.
    What’s an NDKapp? It’s an Android application that uses native libraries. ! Libraries are .so files, usually found inside libs/CPU_ABI/. ! These libs can be generated from native sources inside jni folder, game engines, or required by other 3rd party libraries. ! There is no 100% native application. Even an application purely written in C/C++, using native_app_glue.h, will be executed in the context of the Dalvik Virtual Machine.
  • 20.
    NDK Development ina Nutshell C/C++Code Makefile ndk-build APP_ABI := all or APP_ABI := x86 SDK APIs Java* calls GDB debug through jni Android* Applications Java Framework Java Application JNI Native Libs Bionic C Library NDK APIs
  • 21.
    NDK Anatomy Dalvik VM DalvikBytecode Java Native Interface (JNI) NDK compiled binary Native Platform
  • 22.
    Compatibility with StandardC/C++ • Bionic C Library:
 Lighter than standard GNU C Library
 Not POSIX compliant
 pthread support included, but limited
 No System-V IPCs
 Access to Android* system properties • Bionic is not binary-compatible with the standard C library • It means you generally need to (re)compile everything using the Android NDK toolchain
  • 23.
    Pick One • By default,libstdc++ is used. It lacks: 
 Standard C++ Library support (except some headers)
 C++ exceptions support
 RTTI support • Fortunately, you have other libs available with the NDK: Runtime Exceptions RTTI STL system No No No gabi++ Yes Yes No stlport Yes Yes Yes gnustl Yes Yes Yes
  • 24.
    Compile for allthe platforms. If you have the source code of your native libraries, you can compile it for several CPU architectures by setting APP_ABI to all in the Makefile “jni/Application.mk”:! APP_ABI=all Put APP_ABI=all inside Application.mk Run ndk-build… ARM v7a libs are built ARM v5 libs are built x86 libs are built mips libs are built The NDK will generate optimized code for all target ABIs You can also pass APP_ABI variable directly to ndk-build, and specify each ABI: ndk-build APP_ABI=x86
  • 25.
  • 26.
    NativeActivity • Only nativecode in the project • android_main() entry point running in its own thread • Event loop to get input data and frame drawing messages /** * This is the main entry point of a native application that is using * android_native_app_glue. It runs in its own thread, with its own * event loop for receiving input events and doing other things. */ void android_main(struct android_app* state);
  • 27.
    Add native methodsin Java Declare native methods in your Java classes using the ‘native’ keyword:
 public native String stringFromJNI(); ! Provide a native shared library built with the NDK that contains the methods used by your application:
 ! libMyLib.so Your application must load the shared library (before use… during class load for example):
 static { System.loadLibrary("MyLib"); }
  • 28.
    Variant I: Use„javah“ javah creates JNI header stubs (.h) based on the Java source files from the compiled Java classes files ! Example: > javah –d jni –classpath bin/classes com.example.hellojni.HelloJni ! Generates com_example_hellojni_HelloJni.h file with the following method definition: JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv *, jobject);
  • 29.
    Variant I: Use„javah“ ... { ... tv.setText( stringFromJNI() ); ... } ! public native String stringFromJNI(); static { System.loadLibrary("hello-jni"); } jstring Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI !"); }
  • 30.
    Variant II: JNI_OnLoad •Proven method • No more surprises after methods registration • Less error prone when refactoring • Add/remove native functions easily • No symbol table issue when mixing C/C++ code • Best spot to cache Java class object references
  • 31.
    Variant II: JNI_OnLoad Inyour library name your functions as you wish and declare the mapping with JVM methods: jstring stringFromJNI(JNIEnv* env, jobject thiz) { return env->NewStringUTF("Hello from JNI !"); } ! static JNINativeMethod exposedMethods[] = { {"stringFromJNI","()Ljava/lang/String;",(void*)stringFromJNI}, } ! ()Ljava/lang/String; is the JNI signature of the Java* method, you can retrieve it using the javap utility: > javap -s -classpath binclasses -p com.example.hellojni.HelloJni Compiled from "HelloJni.java“ … public native java.lang.String stringFromJNI(); Signature: ()Ljava/lang/String; …
  • 32.
    Variant II: JNI_OnLoad extern"C" jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; ! jclass clazz = env->FindClass("com/example/hellojni/HelloJni"); if(clazz==NULL) return JNI_ERR; env->RegisterNatives(clazz, exposedMethods, sizeof(exposedMethods)/sizeof(JNINativeMethod)); env->DeleteLocalRef(clazz); } return JNI_VERSION_1_6;
  • 33.
    JNI Primitives Java Type NativeType 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
  • 34.
    Memory Handling ofJava Objects Memory handling of Java* objects is done by the JVM: ! • You only deal with references to these objects • Each time you get a reference, you must not forget to delete it after use • local references are automatically deleted when the native call returns to Java • References are local by default • Global references are only created by NewGlobalRef()
  • 35.
    Creating a JavaString C: jstring string = (*env)->NewStringUTF(env, "new Java String"); ! C++: jstring string = env->NewStringUTF("new Java String"); Memory is handled by the JVM, jstring is always a reference. You can call DeleteLocalRef() on it once you finished with it.
  • 36.
    Convert a jstringinto a C/C++ String const char *nativeString = (*env)>GetStringUTFChars(javaString, null); … (*env)->ReleaseStringUTFChars(env, javaString, nativeString);
  • 37.
    Call a JavaMethod On an object instance: jclass clazz = (*env)->GetObjectClass(env, obj); jmethodID mid = (*env)->GetMethodID(env, clazz, "methodName", "(…)…"); if (mid != NULL) (*env)->Call<Type>Method(env, obj, mid, parameters…); ! Static call: jclass clazz = (*env)->FindClass(env, "java/lang/String"); jmethodID mid = (*env)->GetStaticMethodID(env, clazz, "methodName", "(…)…"); if (mid != NULL) (*env)->CallStatic<Type>Method(env, clazz, mid, parameters…); ! • (…)…: method signature • parameters: list of parameters expected by the Java* method • <Type>: Java method return type
  • 38.
    Handling Java Exceptions //call to java methods may throw Java exceptions jthrowable ex = (*env)->ExceptionOccurred(env); if (ex!=NULL) { (*env)->ExceptionClear(env); // deal with exception } ! (*env)->DeleteLocalRef(env, ex);
  • 39.
    Throw a JavaException jclass clazz = (*env->FindClass(env, "java/lang/Exception"); ! if (clazz!=NULL) (*env)->ThrowNew(env, clazz, "Message"); The exception will be thrown only when the JNI call returns to Java*, it will not break the current native code execution.
  • 40.
  • 41.
  • 42.
    Thanks for yourattention.