Download - NDK Primer (Wearable DevCon 2014)
NDK PrimerRon Munitz
Founder & CEO - The PSCGFounder & CTO - Nubo
[email protected]@android-x86.orghttps://github.com/ronubo/
WearablesDevConMarch 2014
@ronubo
PSCG
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/
© Copyright Ron Munitz 2014
PSCG
About://Ron Munitz
● Distributed Fault Tolerant Avionic Systems○ Linux, VxWorks, very esoteric libraries, 0’s and 1’s
● Highly distributed video routers○ Linux
● Real Time, Embedded, Server bringups○ Linux, Android , VxWorks, Windows, devices, BSPs, DSPs,...
● Distributed Android○ Rdroid? Cloudroid? Too busy working to get over the legal naming, so
no name is officially claimed for my open source
● What currently keeps me busy:○ Running the PSCG, a Embedded/Android consulting and Training ○ Managing R&D at Nubo○ Lecturing at Afeka’s college of Engineering ○ Amazing present, endless opportunities. (Wish flying took less time)
@ronubo
PSCG
Agenda
● Part I: Java○ Java Native Interface (JNI) - Theory○ Hello World Tutorial - Practice
● Part II: JNI in the AOSP● Part III: Native Android Apps
Part IThe Java Native Interface
PSCG
IntroductionThe JNI is a native programming interface. It allows Java code that runs inside a Java Virtual Machine (VM) to interoperate with applications and libraries written in other programming languages, such as C, C++, and assembly. All JVM implementation need to implement the JNI specification to allow compatibility with native code libraries.
JNI to Java ⇔ __asm to C
Motivation● The standard Java class library does not support the
platform-dependent features needed by the application.○ For example NEON, SSSE3, ...
● You already have a library written in another language, and wish to make it accessible to Java code through the JNI.
● You want to implement a small portion of time-critical code in a lower-level language such as assembly.
Wearable Motivation● Reduce CPU cycles (and battery drain rate!) on
intensive computations
● Add designated features for simple add-on hardware without having to support an entire ecosystem for that
● Media processing in Android: All is being done natively this way or another
● Porting legacy code
● Protecting your logic and algorithms from rev-eng
JNI Pitfalls● Subtle errors in the use of JNI can destabilize the entire JVM in
ways that are very difficult to reproduce and debug.
● An application that relies on JNI loses the platform portability Java offers (a partial workaround is to write a separate implementation of JNI code for each platform and have Java detect the operating system and load the correct one at runtime).
● The JNI framework does not provide any automatic garbage collection for non-JVM memory resources allocated by code executing on the native side. Consequently, native side code (such as assembly language) must assume the responsibility for explicitly releasing any such memory resources that it itself acquires.
JNI environment pointers
The JNI interface is organized like a C++ virtual function table. It enables a VM to provide multiple versions of JNI function tables. The JNI interface pointer is only valid in the current thread. Native methods receive the JNI interface pointer as an argument. The VM is guaranteed to pass the same interface pointer to a native method when it makes multiple calls to the native method from the same Java thread.
Loading and linking native methodsNative methods are loaded with the System.loadLibrary() method. In the following example, the class initialization method loads a platform-specific native library in which the native method f is defined:
class Cls { native boolean f(int i, String s);
static { System.loadLibrary(“pkg_Cls”); } } The library pkg_Cls is platform specific. On a Unix derived system it will be named libpkg_Cls.so and on windows system it will be named pkg_Cls.dll.
Loading and linking native methods(cont.)
Dynamic linkers resolve entries based on their names. A native method name is concatenated from the following components:
● the prefix Java_● a mangled fully-qualified class name● an underscore (“_”) separator● a mangled method name
The signature of function f above is, as created by javah:
JNIEXPORT jboolean JNICALL Java_Cls_f(JNIEnv *, jobject, jint, jstring);JNIEnv * - pointer to the JNI interface
jobject - a pointer to the calling java class for a static method or a pointer to the calling java object for non-static method.
Referencing java objects● Primitive types, such as integers, characters, and so on, are
copied between Java and native code (call by value)● Arbitrary Java objects, on the other hand, are passed by
reference.● The VM must keep track of all objects that have been passed to
the native code, so that these objects are not freed by the garbage collector.
● The native code, in turn, must have a way to inform the VM that it no longer needs the objects.
● The JNI divides object references used by the native code into two categories: local and global references. ○ Local references are valid for the duration of a native method
call, and are automatically freed after the native method returns.
○ Global references remain valid until they are explicitly freed.
Referencing java objects(Cont.)
● Objects are passed to native methods as local references. All Java objects returned by JNI functions are local references.
● The JNI allows the programmer to create global references from local references. JNI functions that expect Java objects accept both global and local references. A native method may return a local or global reference to the VM as its result.
● Since the VM needs a certain amount of space to keep track of a local reference, creating too many local references may cause the system to run out of memory.
● Local references are only valid in the thread in which they are created.
● Local references are automatically garbage collected once the function returns.
Invocation API
Used to call java from c/c++ code. The JNI interface pointer (JNIEnv) is valid only in the current thread. Should another thread need to access the Java VM, it must first call AttachCurrentThread() to attach itself to the VM and obtain a JNI interface pointer. Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method. The native thread remains attached to the VM until it calls DetachCurrentThread() to detach itself.
Accessing fields and methods● The JNI allows native code to access the fields and to call the
methods of Java objects. ● The JNI identifies methods and fields by their symbolic names and
type signatures. ● A two-step process factors out the cost of locating the field or
method from its name and signature. For example, to call the method f in class cls, the native code first obtains a method ID, as follows:
jmethodID methodId = env->GetMethodID(cls, “f”, “(ILjava/lang/String;)D”);
The native code can then use the method ID repeatedly without the cost of method lookup, as follows: jdouble result = env->CallDoubleMethod(obj, methodID, 10, str);
Exception handlingThere are two ways to handle an exception in native code:
● The native method can choose to return immediately, causing the exception to be thrown in the Java code that initiated the native method call.
● The native code can clear the exception by calling ExceptionClear(), and then execute its own exception-handling code.
After an exception has been raised, the native code must first clear the exception before making other JNI calls.
JNI types and data structuresJava 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
JNI types and data structures
JVM Type SignaturesType Signature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class ; fully-qualified-class
[ type type[]
( arg-types ) ret-type method type
For example, the Java method:
long f(int n, String s, int[] arr);
has the following type signature:
(ILjava/lang/String;[I)J
JNI types and data structuresJava 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
JNI types and data structures
Tutorial 1: DIY HelloWorld
Linux_x86-64/Oracle JDK 7.0/JNI
Step 1: HelloWorld.javaclass HelloWorld{
private native void print();public static void main(String[] args){
new HelloWorld().print();}static {
System.loadLibrary("HelloWorld"); // Note: No “lib”, No “.so”.}
}
Compile: javac HelloWorld.java .Run: java HelloWorldFail: java.lang.UnsatisfiedLinkError: no HelloWorld in java.library.path . Not surprising…
Step 2: Auto Generate Headers~/edu/jni$ javah HelloWorld # Note: javah takes a class name:~/edu/jni$ cat HelloWorld.h /* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/* Class: HelloWorld Method: print Signature: ()V */
JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *, jobject); // “Java” - because I didn’t use a package.
#ifdef __cplusplus
}
#endif
#endif
Step 3: HelloWorld.c #include <stdio.h> #include "HelloWorld.h" JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *env, jobject obj) { printf("Hello World!\n"); }
Compile: gcc -shared -fPIC -I<YourJvmIncludePaths> HelloWorld.c -o libHelloWorld.soRun: In a couple of slides.Fail: Easily. GCC flags are subtle.
Step 3.5: Build the Shared Library
Let’s have a look at the following concrete example: gcc -shared -fPIC \ -I/usr/lib/jvm/jdk1.7.0/include/linux -I/usr/lib/jvm/jdk1.7.0/include \HelloWorld.c -o libHelloWorld.so
-shared: Create a shared object. (In Windows: a “DLL”)-fPIC: Position Independent Code - Always create Shared Object with that flag.-I<et. al>: Location of jni.h, and files included from there. -o libHelloWorld.so: Everything between “lib” and “.so” must match the name in loadLibrary().
Step 4: Run
java -Djava.library.path=. HelloWorld
Note: It is important to tell java where to look for your shared libraries. Otherwise you will see the same error listed in Step 1.
Hello World!
Part IISystemServer’s native side:
libandroid_services
PSCG
The NDK in a nutshell
● Available as a JNI “SDK” to Android application developers.○ In the platform itself there is no need for it, just as
there is no need for the Android SDK● Eases porting of existing Linux code to
Android○ As long as it is not heavily depended on glibc
features, X, QT/GTK etc.● Enables common code for multiple
architectures ○ On the nominal portable C/C++ code case ○ @see Application.mk
JNI in the Android platform
● @see frameworks/base/services/java/com/android/server/SystemServer.java
● This is where the Java Android OS starts.● And it is heavily relying on native functions
SystemServer.java revisitedpublic class SystemServer { private static final String TAG = "SystemServer"; //...
private static native void nativeInit(); /*Called to initialize native system services.*/ public static void main(String[] args) { // ...
// Set runtime libary property (Dalvik/ART/..) ...
// Set initial system time (to workaround some negative value bugs) ...
// Enable profiler snapshot if profiler is enabled ...
// Take care of HeapSize for the System Server which never dies ... // ...
System.loadLibrary("android_servers"); Slog.i(TAG, "Entered the Android system server!");
nativeInit(); /* Initializes native services - This is a JNI call to frameworks/base/services/jni/com_android_server_SystemServer.cpp …
See next slide*/
ServerThread thr = new ServerThread();
thr.initAndLoop(); // Note to self: so long init2()!!!
}
}
com_android_server_SystemServer.cpp
● Path: frameworks/base/services/jni/● namespace: Android● C, C++, java, JNI:
○ com/android/server/SystemServer.java○ com_android_server_SystemServer.cpp○ Note: Name convention should suffice.
However, when adding to the SystemServer, it is advised to also jniRegisterNativeMethods (@see libnativehelper/…)
● As per the code itself… static void android_server_SystemServer_nativeInit(JNIEnv* env, jobject clazz) { char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsensorservice", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { SensorService::instantiate(); // Start the sensor service which is pure native } // @see frameworks/native/services/sensorservice}
onload.cpp
● jint JNI_OnLoad(JavaVM* vm, void* reserved) Called by default by the JVM upon System.loadLibrary()
● Loads the JNI Environment (uses JNI_VERSION_1_4)● Registers native servers by calling
register_android_server_<Name>(env); <Name> can be one of:PowerManagerService SerialServiceInputApplicationHandle InputWindowHandleInputManager LightsServiceAlarmManagerService UsbDeviceManagerUsbHostManager VibratorServiceSystemServer location_GpsLocationProvider location_FlpHardwareProvider connectivity_VpnAssetAtlasService ConsumerIrService
register_android_server_<Name> and jniRegisterNativeMethods
● As we saw, libandroid_servers’s JNI_OnLoad() registers each of the JNI servers.
● Each explicitly registers the exported JNI constructs, by providing their signatures to libnativehelper’s jniRegisterNativeMethods() method
● Which in turns calls JNI’s RegisterNatives() method.● This is also essential because the AOSP uses naming
conventions that do not start with the “Java_” prefix, and therefore cannot be invoked unless explicitly registered by RegisterNatives()
JNI_onLoad()
● The context of the VM is available only in the called thread.
● Which means that calling back Java is possible only if:○ It is a call back from a thread invoked by Java○ Somewhere a reference is stored globally for all/for
relevant threads● Do JVM caching on onLoad()● Free on onUnload()
Part IIIWriting an Android App
PSCG
Native project directory structure
● Source Control:○ ./ - Root. Home of manifest, ant/gradle, ...○ src - Java source files○ lib - Libaries. Including generated libraries and .so’s○ res - Resources○ assets - Assets (raw resources etc.)○ jni - Native code. Home of *.c*, *.h*, *.mk
■ Android.mk - Makefile■ Application.mk - Application definition (APP_*)
● Generated○ gen - generated java files○ obj - Native object files○ bin - Binaries
NDK App development with Android
1. Java code - as in Java’s JNI.2. Native code - as in Java’s JNI3. javah - provide class path (bin/…)4. Makefiles
a. Android.mk - Android makefile.i. e.g. include $(BUILD_SHARED_LIBRARY)
b. Application.mk - Application definitionsi. e.g. APP_ABI := all -
5. ndk-build - builds native code6. ant/gradle - builds APK and bundles .so’s in
lib
Tutorial 2: DIY HelloWorld
${NDK}/samples/hello-jni
jni/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jniLOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_ABI := x86 armeabi
obj/local/x86/libhello-jni.soobj/local/armeabi/libhello-jni.so
ndk-build
src/com/example/hellojni/HelloJni.java
package com.example.hellojni;
import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;
public class HelloJni extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setText( stringFromJNI() ); setContentView(tv);
}
public native String stringFromJNI(); public native String unimplementedStringFromJNI(); // Catch: Call --> exception static { System.loadLibrary("hello-jni"); }}
The Native App Glue
● Enables writing fully native applications○ That is 0 java files
● Usually good for porting games● Or porting other heavy event machine logic● Requires implementation of your own Looper● Requires a manifest declaration:
<activity android:name="android.app.NativeActivity" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden"> <!-- Tell NativeActivity the name of or .so --> <meta-data android:name="android.app.lib_name" android:value="native-activity" />
...
Publishing Applications
● Two approaches each has its pros and cons● The first: Compile with all the libraries you
can (APP_ABI := all)○ Easier to maintain.○ At the cost of larger (and sometimes huge) APK’s
● The second: Create version per architecture○ Create application with a slightly modifed version
code at the MSB.○ This is the PlayStore way of identifying multiple
APKs for the same version
References● http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/types.html● http://developer.android.com/tools/sdk/ndk/index.html● Introduction to Android Internals - Ron Munitz, Afeka ● Native Android Programming course - @thePSCG