The Android Native Development Kit (NDK) allows developers to write and embed native code like C/C++ in Android applications. It provides toolchains and headers to allow compilation of native code into libraries that can be called from Java code using the Java Native Interface. The NDK benefits applications requiring intensive CPU operations, games, or porting existing C/C++ code to Android. However, native code also increases complexity, so the NDK should only be used when truly needed rather than for preference of language. Sample projects demonstrate using the NDK to incorporate native code for tasks like threading and improving performance of algorithms.
2. Introduction
• What is the Android NDK?
• Write, Compile and Embed Native code into Apps
• Porting existing C/C++ code
3. Android SDK
• Write Apps in Java
• Resources and
compiled codes
packed In APK
• Get access to the
Android Framework
• Interpreted by the
Dalvik VM
4. What is the Android NDK?
• A tool to Write, Compile and Embed
Native code in Apps
• Usable for 3 types architecture:
• ARM, X86 and MIPS
• Makefile and GDB Debugger support!
5. What is the Android NDK?
• Download NDK from this page:
• https://developer.android.com/tools/sdk/ndk/index.
html
6. Why use Android NDK?
• It is for you…. if:
• Need performance (Games or intensive Apps)
• Control memory allocation and alignment yourself
• Write CPU-intensive operations
• Exceed Java Apps memory limitations
• Port C/C++ code
• Reuse legacy codes
7. Why use Android NDK?
• the NDK will not benefit most apps!
• Best for game engines
• CPU intensive workload
• Signal processing
• Physics simulation
8. Why use Android NDK?
• It is not for you… if:
• Think Java is complicated:
• C/C++ would make it worse
• JNI is a headache
10. Android Studio
• Not an easy setup for Android Studio.
• Gradle hack!
• Check
https://bitbucket.org/khiemkimxuan/ndk
11. Creating a NDK project with
Android Studio
• In short:
1. Create an Android project
2. Add new «JNI» folder to the Project.
3. Add ndk.dir=location of ndk, to local.properties file
4. Add hacks on gradle build file (check the link from the previous slide!)
5. C/C++ files and Makefiles must be in the Project’s jni folder
6. Compile it!
1. Binary SO librares generated in /lib/armeabi
7. Alternatively, use standalone toolchain to cross compile C/C++ or
Assembly file and run it on adb shell!
12. Write your C/C++ source file
#include <jni.h>
#include <string.h>
JNIEXPORT jstring JNICALL Java_com_myproject_MyActivity_getMyData
(JNIEnv* pEnv, jobject pThis)
{
return (*pEnv)->NewStringUTF(pEnv, "My native project talks C, not C++
ok? Pointer difference!!");
}
#include <jni.h>
#include <string.h>
extern "C"
JNIEXPORT jstring JNICALL Java_com_myproject_MyActivity_getMyData
(JNIEnv* pEnv, jobject pThis)
{
return pEnv->NewStringUTF("My native project talks C++, not C ok? Pointer
difference!!");
}
}
C code
C++ code
14. Write your Java Activity
package com.myproject;
Import ..............
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
setTitle(getMyData());
}
public native String getMyData();
static {
System.loadLibrary("mylib");
}
}
15. Write a Makefile
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := mylib
LOCAL_SRC_FILES := MyActivity.c
include $(BUILD_SHARED_LIBRARY)
LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv2
# Typical filename should end with .mk. To build, add ndk directory to
# System PATH and run ndk-build inside the jni folder.
# You should also create Application.mk file that describes APP_ABI
#and APP_PLATFORM
16. Queen Game - example C vs Java
static int isConsistent(int q[], int n) {
int i;
for(i = 0; i < n; i++) {
if(q[i] == q[n]) return FALSE;
if(q[i] - q[n] == (n - i)) return
FALSE;
if(q[n] - q[i] == (n-i)) return
FALSE;
}
return TRUE;
}
static void enumerateRec(int board[], int n)
{
int board_length = sizeof(board);
if(n == board_length) {
//print_result(board); }
else {
int i = 0;
for(i = 0; i < board_length; i++) {
board[n] = i;
if(isConsistent(board,n) == TRUE)
enumerateRec(board,n+1);
}
}
}
01.05.201516
static void enumerate(int N) {
int board[N];
memset(board,0,sizeof(board));
enumerateRec(board, 0);
}
JNIEXPORT void JNICALL
Java_com_example_kkh_myapplication_MainActivity_
runQueen(JNIEnv *env, jobject obj,
jint arg) {
int n = (int)arg;
enumerate(n);
}
17. Queen Game - example C vs Java
01.05.201517
public class MainActivity extends
Activity {
public native String
stringFromJNI();
public native void runQueen(int N);
static {
System.loadLibrary("main");
}
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
runQueen(200000);
}
…
18. Queen Game - example C vs Java
01.05.201518
public static boolean isConsistent(int[]
q, int n) {
for (int i = 0; i < n; i++) {
if (q[i] == q[n]) return
false;
if ((q[i] - q[n]) == (n - i))
return false;
if ((q[n] - q[i]) == (n - i))
return false;
}
return true;
}
…
public static void enumerate(int[] q,
int n) {
int N = q.length;
if (n == N) printQueens(q);
else {
for (int i = 0; i < N; i++) {
q[n] = i;
if (isConsistent(q, n))
enumerate(q, n + 1);
}
}
}
public static void enumerate(int N) {
int[] a = new int[N];
enumerate(a, 0);
}
}
20. Measure Performance C vs Java
01.05.201520
• Queen Game:
• C: 0.00001
• Java: 0.00222
• Fibonacci:
• C Result: 0.01251
• Java: 0.04512
21. The C/C++ side
...
jint JNICALL Java_com_myproject_MyStore_addition
(JNIEnv *pEnv, jobject pObj, jint pa, jint pb) {
return pa + pb;
}
...
• JNIEnv allows manipulating the Virtual Machine (functions are mapped to Java
methods)
• Java types are mapped to JNI native types
• Primitives can be converted to classic C/C++ primitives
• Mainly Java Reflection:
...
struct JNINativeInterface {
jclass (*FindClass)(JNIEnv*, const char*);
jint (*ThrowNew)(JNIEnv *, jclass, const char *);
jobject (*NewGlobalRef)(JNIEnv*, jobject);
jobject (*NewLocalRef)(JNIEnv*, jobject);
jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...);
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const
char*);
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID,
...);
jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID,
...);
...
};
22. Java Primitive Type Data
Java Type JNI Type C Type
boolean jboolean unsigned char
byte jbyte signed char
char jchar unsigned short
double jdouble double
float jfloat float
byte jbyte signed char
int jint int
long jlong long long
short jshort short
23. Java Reference Types Mapping
Java Type Native Type
java.lang.Class jclass
java.lang.Throwable jthrowable
java.lang.String jstring
Other objects jobject
java.lang.Object[] jobjectArray
boolean[] jbooleanArray
byte[] jbyteArray
char[] jcharArray
short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray
Other arrays Jarray
24. Usage of Libraries
• Android Log library
• Log message to LogCat
__android_log_print(ANDROID_LOG_INFO,"TAG","Message men");
• Other C Libraries
• Memory management
• File management
• NativeThreads
• Time
• OpenGL
• ...
25. Bionic API (1/3) – Introduction
• Bionic is the POSIX standard C library for Android
Platforms
• Best suited for mobile computing and provides
lightweight wrapper around kernel.
• Bionic provides C standard library macros, type
definitions, functions etc.
• Not every function in the standard C library is
supported by Bionic.
01.05.201525
26. Bionic API (2/3) – Memory Management
• Dynamic Memory Management for C
• Always include standard C library header:
• #include<stdlib.h>
• Allocate Dynamic Memory in C:
• void* malloc(size_t size);
• Deallocate Dynamic Memory in C:
• void free(void* memory);
• Changing Dynamic Memory Allocation in C:
• void* realloc(void* memory, size_t size);
01.05.201526
27. Bionic API (3/3) – Standard File I/O
• Standard Streams:
• stdin: Standard input stream
• stdout: Standard output stream
• stderr: Standard error stream
• Always include standard I/O C library header:
• #include<stdio.h>
• Most file I/O functions can be used:
• write: fopen, fwrite, fputs, fputc....
• read: fread, fgets, fgetc, fscanf....
• seek: fseek
01.05.201527
28. Native Threads (POSIX Threads)
• A part of the Bionic C standard library
• #include <pthread.h>
• Pthread functions:
• pthread_create, pthread_join,
• The POSIX threads are not known to the Java VM.
• Solution? Attach them to the Java VM!
• Use jint JNI_OnLoad (JavaVM *vm, void* reserved) function as it gets
invoked by the virtual machine when the shared library is loaded.
• Cannot share JNIEnv, it’s thread local
• Use AttachCurrentThread, DetachCurrentThread the thread
before exit
01.05.201528
29. Native Thread Example C side
jint JNI_OnLoad (JavaVM* vm, void* reserved) {
jvm1 = vm;
return JNI_VERSION_1_6;
}
void *run_task(void *args) {
JNIEnv* env = NULL;
int n = (*jvm1)->AttachCurrentThread(jvm1,&env, NULL);
if (n == 0) {
jstring msg = (*env)->NewStringUTF(env,"Yes Thread
Running.");
(*env)->CallVoidMethod(env, obj1, mid1, msg);
(*env)->DeleteGlobalRef(env,obj1);
(*jvm1)->DetachCurrentThread(jvm1);
}
}
01.05.201529
31. Native Thread Example in Java
public class MainActivity extends
Activity {
@InjectView(R.id.thread_start_button)
Button mStartNativeThreadButton;
public static MainActivity instance;
public native void
startNativeThread();
static {
System.loadLibrary("main");
}
@Override
protected void onCreate(Bundle
savedInstanceState) {
…
ButterKnife.inject(this);
instance = this;
}
01.05.201531
@OnClick(R.id.thread_start_button)
public void
startThreadButton(View v) {
if(v.getId() ==
R.id.thread_start_button) {
startNativeThread();
}
}
public void setMsg(final String
msg) {
Toast.makeText(instance, msg,
Toast.LENGTH_LONG).show();
}
32. C/C++ App (1/3)
• Write only C or C++ app for Android rather than having
any Java code.
• Need to specify native_app_glue at the local static library
flag.
• Add app_dummy() at android_main() to make sure that
the glue isn’t stripped
• Make sure to add android.app.NativeActivity as
Android name for the Activity.
• http://developer.android.com/reference/android/app/NativeActivity.html
01.05.201532
33. C/C++ App (2/3)
• In the AndroidManifest.xml ( or create an Activity file and
extend android.app.NativeActivity and load the library)
<activity
android:name="android.app.NativeActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:uiOptions="none">
<meta-data
android:name="android.app.lib_name"
android:value="main" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
01.05.201533
34. C/C++ App (3/3)
• Example Brick Breaker (download code at the link
given from slide 10)
01.05.201534
36. Porting existing C/C++ code
• Cross-compile on cmd or a terminal in Linux!
• Commands to cross-compile (add to environment path and remember to
include sysroot platforms):
• ARM: arm-linux-androideabi-gcc/g++-<version> --sysroot <path>
<files.c/cpp>…
• Intel X86: i686-linux-android-gcc/g++ --sysroot <path>
<files.c/cpp>…
• MIPS: mipsel-linux-android-gcc/g++ --sysroot <path>
<files.c/cpp>…
• Build your own NDK Makefile
• Example project for porting C code to an existing app:
• The Emerald Programming Language:
https://bitbucket.org/khiemkimxuan/emerald-lite
37. Example of Standalone Toolchain
• Simple Socket example from Beej Client and Server
• Cross compile Client and port it!
• Follow these steps:
• <architecture-gcc> -fPIE -pie --sysroot <path of sysroot> filename.c -o
filename
• adb push filename /local/data/tmp/filename
• adb shell chmod 0755 /local/data/tmp/filename
• adb shell <path to filename>
• ./filename
• Binary files need permission (chmod that file(s)!).
• Android runtime runs binaries only if they have
permission.
01.05.201540
38. Thank you for listening!
“Before downloading the NDK, you should understand that the NDK
will not benefit most apps. As a developer, you need to balance its
benefits against its drawbacks. Notably, using native code on Android
generally does not result in a noticable performance improvement, but it
always increases your app complexity. In general, you should only use
the NDK if it is essential to your app—never because you simply prefer
to program in C/C++.”
– Android Developers
01.05.201541
APK = Android Application Package
Dalvik VM = specialized for mobile embedded systems to run applications
The Android runtime also provides a set of core libraries which enable Android application developers to write Android applications using standard Java programming language.
However, we can still use SDK without NDK
Ability to cross-compile C/C++ and even Assembly code to Android embedded systems
Cross compilation toolchain based on GCC
However, we can still use SDK without NDK
Ability to cross-compile C/C++ and even Assembly code to Android embedded systems
Cross compilation toolchain based on GCC
Most Android apps should just work without any changes under ART. However, some techniques that work on Dalvik do not work on ART. For information about the most important issues, see Verifying App Behavior on the Android Runtime (ART). Pay particular attention if:
Your app uses Java Native Interface (JNI) to run C/C++ code.
You use development tools that generate non-standard code (such as some obfuscators).
You use techniques that are incompatible with compacting garbage collection.
Ahead-of-time (AOT) compilation
GC improved
" you should understand that the NDK will not benefit most apps. As a developer, you need to balance its benefits against its drawbacks. Notably, using native code on Android generally does not result in a noticable performance improvement, but it always increases your app complexity. In general, you should only use the NDK if it is essential to your app—never because you simply prefer to program in C/C++." - Android Developers
Need a way to tie Java and Native code together!
Java Native Interface(JNI), a 2-way bridge:
Java code can call Native code
Native code can callback Java code
Based on a Reflection-like API
Extern C tells the compiler to give the method C linkage recognized only by C++.
JNIEXPORT have to have it so that it appears in the built binary (.so file)
JNICALL tell which method to use from C to Java
JNIEnv is the pointer to the interface of JVM
jstring JNICALL defines the return of the method as a Java compatible string
N*n chessboard no queen is on the same row, column or diagonal as another queen (no queen should threaten another queen).
v
Android.mk is for one individual solution.
if many makefiles. Application.mk should be used because it is for the whole project.
reflection for accessing and manipulating classes, fields, methods, and constructors.
Realloc only works when you have allocated memory
Threads must attach themselves to the
Java VM to interact with the java code.
If you start the Posix thread, is unable to interact with the Android Java layer, mainly because the POSIX threadcannot directly call JNIEnv . So we need to put the native thread associated with the JVM, it can obtain JNIEnv. No JNIEnv is not a callback JAVA layer method.
Compiler strips away the native_app_glue.o, that is why we have to add app_dummy to avoid that
Our activity is the built-in NativeActivity framework class.
Compiler strips away the native_app_glue.o, that is why we have to add app_dummy to avoid that
arm-linux-androideabi-as test.s -o test.o
arm-linux-androideabi-ld --sysroot \path\to\android-ndk\platforms\android-3\arch-arm -o test test.o -lc
push to phone :
adb push test /data/local/tmp/test
adb shell chmod 0755 /data/local/tmp/test
adb shell
cd /data/local/tmp
./test
On ARM processors you have 16 registers. Actually, that is not entirely true. ARM processors have 32 registers, each 32 bits wide. ARM processors have different programming modes to distinguish user-level and system-level access. Only some registers are visible in each mode. In the user-level mode you can access 16 registers. This is the mode you will use most frequently, so you can ignore the entire mode stuff for now. By the time you need to write Linux device drivers, you will be well past this introduction.The registers are called r0-r15, and the last four are special.r12: IP, or Intra-Procedure call stack register. This register is used by the linker as a scratch register between procedure calls. A procedure must not modify its value on return. This register isn't used by Linux gcc or glibc, but another system might.r13: SP, or Stack Pointer. This register points to the top of the stack. The stack is area of memory used for local function-specific storage. This storage is reclaimed when the function returns. To allocate space on the stack, we subtract from the stack register. To allocate one 32-bit value, we subtract 4 from the stack pointer.r14: LR, or Link Register. This register holds the return value of a subroutine. When a subroutine is called, the LR is filled with the program counter.r15: PC, or Program Counter. This register holds the address of memory that is currently being executed.There is one more register, the Current Program Status Register (CPSR) that contains values indicating some flags like Negative, Zero, Carry, etc. We'll visit it later, you can't read and write it like a normal register anyway.
arm-linux-androideabi-as test.s -o test.o
arm-linux-androideabi-ld -s -o test test.o
On ARM processors you have 16 registers. Actually, that is not entirely true. ARM processors have 32 registers, each 32 bits wide. ARM processors have different programming modes to distinguish user-level and system-level access. Only some registers are visible in each mode. In the user-level mode you can access 16 registers. This is the mode you will use most frequently, so you can ignore the entire mode stuff for now. By the time you need to write Linux device drivers, you will be well past this introduction.The registers are called r0-r15, and the last four are special.r12: IP, or Intra-Procedure call stack register. This register is used by the linker as a scratch register between procedure calls. A procedure must not modify its value on return. This register isn't used by Linux gcc or glibc, but another system might.r13: SP, or Stack Pointer. This register points to the top of the stack. The stack is area of memory used for local function-specific storage. This storage is reclaimed when the function returns. To allocate space on the stack, we subtract from the stack register. To allocate one 32-bit value, we subtract 4 from the stack pointer.r14: LR, or Link Register. This register holds the return value of a subroutine. When a subroutine is called, the LR is filled with the program counter.r15: PC, or Program Counter. This register holds the address of memory that is currently being executed.There is one more register, the Current Program Status Register (CPSR) that contains values indicating some flags like Negative, Zero, Carry, etc. We'll visit it later, you can't read and write it like a normal register anyway.
arm-linux-androideabi-as test.s -o test.o
arm-linux-androideabi-ld -s -o test test.o
On ARM processors you have 16 registers. Actually, that is not entirely true. ARM processors have 32 registers, each 32 bits wide. ARM processors have different programming modes to distinguish user-level and system-level access. Only some registers are visible in each mode. In the user-level mode you can access 16 registers. This is the mode you will use most frequently, so you can ignore the entire mode stuff for now. By the time you need to write Linux device drivers, you will be well past this introduction.The registers are called r0-r15, and the last four are special.r12: IP, or Intra-Procedure call stack register. This register is used by the linker as a scratch register between procedure calls. A procedure must not modify its value on return. This register isn't used by Linux gcc or glibc, but another system might.r13: SP, or Stack Pointer. This register points to the top of the stack. The stack is area of memory used for local function-specific storage. This storage is reclaimed when the function returns. To allocate space on the stack, we subtract from the stack register. To allocate one 32-bit value, we subtract 4 from the stack pointer.r14: LR, or Link Register. This register holds the return value of a subroutine. When a subroutine is called, the LR is filled with the program counter.r15: PC, or Program Counter. This register holds the address of memory that is currently being executed.There is one more register, the Current Program Status Register (CPSR) that contains values indicating some flags like Negative, Zero, Carry, etc. We'll visit it later, you can't read and write it like a normal register anyway.
arm-linux-androideabi-gcc-4.9 -fPIE -pie --sysroot C:\android-ndk-r10d\platforms\android-21\arch-arm\
test.c -o test
Remember CHMOD 0755
SYSROOT IS AN ENVIRONMENT VARIABLE TO LOCATE DEVICE SYSTEM LIBRARIES
adb push /path/to/executable /data/local/tmp
adb shell chmod 0755 /data/local/tmp/executable
adb shell 'cd /data/local/tmp && ./executable'
C:\Users\kkh\Downloads>arm-linux-androideabi-gcc-4.9 -fPIE -pie --sysroot %NDK_SYSROOT% client.c -o client
adb push client /data/local/tmp/client
adb shell chmod 0755 /data/local/tmp/client
Log into adb shell and run it on /data/local/tmp
i686-linux-android-gcc-4.9 -fPIE -pie --sysroot $env:X86_SYSROOT client.c -o client