Native code is usually compiled into a shared library and loaded before the native methods can be called. This recipe covers how to load native libraries and register native methods.
Please read the recipes in Chapter 1, Hello NDK, to set up the Android NDK development environment if you haven't done so already.
The following steps will show you how to build an Android application that demonstrates loading native libraries and registering native methods:
NativeMethodsRegister
. Select Create new project in workspace. Then, click on Next.cookbook.chapter2
. Select the Create Activity checkbox, and specify the name as NativeMethodsRegisterActivity
. Set the value for Minimum SDK as 5 (Android 2.0). Then, click on Finish.NativeMethodsRegister
project, then select New | Folder. Enter the name jni
in the pop-up window, then click on Finish.jni
folder under the NativeMethodsRegister
project, then select New | File. Enter nativetest.c
as the value for File name, then click on Finish.nativetest.c
:#include <android/log.h> #include <stdio.h> jint NativeAddition(JNIEnv *pEnv, jobject pObj, jint pa, jint pb) { return pa+pb; } jint NativeMultiplication(JNIEnv *pEnv, jobject pObj, jint pa, jint pb) { return pa*pb; } JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pVm, void* reserved) { JNIEnv* env; if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6)) { return -1; } JNINativeMethod nm[2]; nm[0].name = "NativeAddition"; nm[0].signature = "(II)I"; nm[0].fnPtr = NativeAddition; nm[1].name = "NativeMultiplication"; nm[1].signature = "(II)I"; nm[1].fnPtr = NativeMultiplication; jclass cls = (*env)->FindClass(env, "cookbook/chapter2/NativeMethodRegisterActivity"); // Register methods with env->RegisterNatives. (*env)->RegisterNatives(env, cls, nm, 2); return JNI_VERSION_1_6; }
NativeMethodRegisterActivity.java
:public class NativeMethodRegisterActivity extends Activity { … … private void callNativeMethods() { int a = 10, b = 100; int c = NativeAddition(a, b); tv.setText(a + "+" + b + "=" + c); c = NativeMultiplication(a, b); tv.append(" " + a + "x" + b + "=" + c); } private native int NativeAddition(int a, int b); private native int NativeMultiplication(int a, int b); static { //use either of the two methods below //System.loadLibrary("NativeRegister"); System.load("/data/data/cookbook.chapter2/lib/libNativeRegister.so"); } }
TextView
in the res/layout/activity_native_method_register.xml
file as follows:<TextView android:id="@+id/display_res" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:padding="@dimen/padding_medium" android:text="@string/hello_world" tools:context=".NativeMethodRegisterActivity" />
Android.mk
under the jni
folder with the following content:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := NativeRegister LOCAL_SRC_FILES := nativetest.c LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
jni
folder under our project, and type ndk-build
to build the native library.This recipe describes how to load a native library and register native methods:
java.lang.System
class provides two methods to load native libraries, namely loadLibrary
and load
. loadLibrary
accepts a library name without the prefix and file extension. For example, if we want to load the Android native library compiled as libNativeRegister.so
in our sample project, we use System.loadLibrary("NativeRegister")
. The System.load
method is different. It requires the full path of the native library. In our sample project, we can use System.load("/data/data/cookbook.chapter2/lib/libNativeRegister.so")
to load the native library. The System.load
method can be handy when we want to switch between different versions of a native library, since it allows us to specify the full library path.We demonstrated the usage of both the methods in the static initializer of the
NativeMethodRegisterActivity.java
class. Note that only one method should be enabled when we build and run the sample application.
JNIEnv
. The JNIEnv
interface pointer is pointing to thread-local data, which in turn points to a JNI function table shared by all threads. This can be illustrated using the following diagram:The JNIEnv
interface pointer is the gateway to access all pre-defined JNI functions, including the functions that enable the native code to process Java objects, access Java fields, invoke Java methods, and so on. The RegisterNatives
native function we're going to discuss next is also one of them.
The JNIEnv
interface pointer points to thread-local data, so it cannot be shared between threads. In addition, JNIEnv
is only accessible by a Java thread. A native thread must call the JNI function
AttachCurrentThread
to attach itself to the VM, to obtain the JNIEnv
interface pointer. We will see an example of this in the Manipulating classes in JNI recipe in this chapter.
RegisterNatives
JNI function to register the native methods. The RegisterNatives
function has the following prototype:jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);
The clazz
argument is a reference to the class in which the native method is to be registered. The methods
argument is an array of the JNINativeMethod
data structure. JNINativeMethod
is defined as follows:
typedef struct { char *name; char *signature; void *fnPtr; } JNINativeMethod;
name
indicates the native method name, signature
is the descriptor of the method's input argument data type and return value data type, and fnPtr
is the function pointer pointing to the native method. The last argument, nMethods
of RegisterNatives
, indicates the number of methods to register. The function returns zero to indicate success, and a negative value otherwise.
RegisterNatives
is handy to register a native method implementation for different classes. In addition, it can simplify the native method name to avoid careless mistakes.
The typical way of using RegisterNatives
is in the
JNI_OnLoad
method as shown in the following template. JNI_OnLoad
is called when the native library is loaded, so we can guarantee that the native methods are registered before they're invoked:
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* pVm, void* reserved) { JNIEnv* env; if ((*pVm)->GetEnv(pVm, (void **)&env, JNI_VERSION_1_6)) { return -1; } // Get jclass with env->FindClass. // Register methods with env->RegisterNatives. return JNI_VERSION_1_6; }
We demonstrated the usage of the preceding template in the
JNI_OnLoad
method of our sample code, where we registered two native methods to add and multiply two input integers respectively. The execution result shown earlier proves that the Java code can invoke the two registered native methods successfully.
Note that this example uses some JNI features that we're going to cover in later recipes, including the FindClass
function and field descriptors. It is alright if don't fully understand the code at this stage. You can always go back to it after learning more about those topics.