Before Start
When we develop Android projects, Java codes can handle most of the cases. However, we may need to run C/C++ codes under some circumstances:- Some of your methods have performance issues.
- Re-use some open-sourced codes written in C/C++.
- The methods written in C/C++ handle CPU-intensive tasks such as signal process, machine learning algorithms, etc.
Again, running C/C++ codes on Android does NOT guarantee performance improvement, it somethings make your application slower. Only transform the methods that are really CPU-intensive.
What is Android NDK
Android NDK stands for Android Native Development Kit. As Java run the codes in the JVM, no matter what the hardware is; however, it does matter to C/C++ codes, which need to be compiled in different ways (implicitly) according to the hardware architecture before we can run it, that is why we call it "native".
How it work
- Install the NDK to compile the C/C++ code to native libraries
https://developer.android.com/tools/sdk/ndk/index.html - Declare an "interface" as a bridge from C/C++ functions to Java methods
- Declare the Java methods in your application, then use it like (most) normal Java methods
Case study
Instead of using a "HelloWorld" for demonstration, I would like to trace a open-source project for your reference, and that is how I learned by the way.Libsvm-androidjni -- LIBSVM for android jni environment
https://github.com/cnbuff410/Libsvm-androidjni
Libsvm is one of the most powerful tools for SVM and widely used in many classification problems in the world, and it is definitely CPU-intensive. To make this tutorial simple, I just introduce the svm-train function here in a bottom up approach (target the C/C++ codes first, then the interface and how to call the Java methods and so on).
Original source code
/jni/src/svm/svm-train.cpp
/jni/src/svm/svm-train.h
The entry in the svm-train.cpp file is
int svmtrain(int argc, char **argv)
that is the function we want to link to Java
Interface C/C++ code
/jni/src/train.cpp
/jni/src/train.h
int train(const char *trainingFile, int kernelType, int cost, float gamma,
int isProb, const char *modelFile)
This function receive some parameters from another interface C/C++ code, and call the svmtrain(int argc, char **argv) in the original source code to do the real work.
Interface C/C++ (2)
/jni/jni/info_kunli_androidlibsvmexample_AndroiLibsvmExampleActivity.cppThis is an unusual C/C++ that really link the native codes (C/C++) to you Android (Java) code
static jint trainClassifier(JNIEnv *env, jobject obj, jstring trainingFileS,
jint kernelType, jint cost, jfloat gamma, jint isProb, jstring modelFileS) {
jboolean isCopy;
const char *trainingFile = env->GetStringUTFChars(trainingFileS, &isCopy);
const char *modelFile = env->GetStringUTFChars(modelFileS, &isCopy);
int v = train(trainingFile, kernelType, cost, gamma, isProb, modelFile);
env->ReleaseStringUTFChars(trainingFileS, trainingFile);
env->ReleaseStringUTFChars(modelFileS, modelFile);
return v;
}
This is the native function that will be compiled and imported to the Android Java codes. It receives the parameters from your other Java codes, and pass them to int train() in the interface C/C++ function mentioned above.
Three rules observed here
- Besides the real parameters, please add JNIEnv *env and jobject obj as the first two parameters
- Add j in front of type identifier; use Array instead of []. For instance, int becomes jint, int[] becomes jintArray
- Use objectArray for any multi-dimensional array. For instance both int[][] and float[][] will become objectArray
- Convert the strings before and after use them.
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"trainClassifierNative", "(Ljava/lang/String;IIFILjava/lang/String;)I",
(void*)trainClassifier},
};
Declare the real type of parameters in the native function. That is, the real type that are accepted in the compiled native method in your Java codes depend on this statement.Put the parameters in the () pair, followed by the return value type
Several mappings here
[type in Java]:[identifier here]
int: I
float: F
double: D
String: Ljava/lang/String; (do not forget the semicolon)
float[][]: [[F
int[][]: [[I
For example:
float HelloWord(int i, String s1, String s2, int[][] arrayI) becomes
"(ILjava/lang/String;Ljava/lang/String;[[I)F"
Register this interface and native methods
int register_Signal(JNIEnv *env)
Change the second parameter in static int jniRegisterNativeMethods() with this file's title (without the extension). For example:
int register_library(JNIEnv *env) {
return jniRegisterNativeMethods(env, "info/kunli/AndroidLibsvmExampleActivity",
sMethods, sizeof(sMethods) / sizeof(sMethods[0]));
}
}
Again, you must replace "info/kunli/AndroidLibsvmExampleActivity" to the file name where you register the library.
Link the interface java codes with Java Virtual Machine
onload.cpp
Makefile of the NDK
jni/src/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_C_INCLUDES += $(JNI_H_INCLUDE)
LOCAL_SRC_FILES := \
jni/onload.cpp \
jni/info_kunli_androidlibsvmexample_AndroidLibsvmExampleActivity.cpp \
src/train.cpp \
src/svm/svm-train.cpp \
src/svm/svm.cpp
LOCAL_NDK_VERSION := 4
LOCAL_SDK_VERSION := 10
LOCAL_MODULE := libsignal
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
Call NDK to compile the cpp files into a native library (*.so)
libsignal is the name of compiled native library here
jni/src/Application.mk
Set up the hardware architecture
Call NDK to compile the library
ndk-build clean
ndk-build
ndk-build
Overview of the native library building process (top-down)
NDK configuration
Android.mk
Application.mk
Linking JVM to native library
onload.cpp
Set up the interface between Java and native codes
info_kunli_androidlibsvmexample_AndroiLibsvmExampleActivity.cpp
Call the native function as Java methods in the Android project
AndroidLibsvmExampleActivity.java
Note:
If you are familiar with libsvm, you probably found that the lasted version when I am writing this post (May/2014) does not fully support the original LIBSVM well. That is why I want (have) to learn how the NDK work so that I can expand it via the interface codes (not the original C/C++ codes).
Thank you for sharing run C/C++ program on Android
ReplyDelete