Let's continue our Store
by calling back the interface we defined from native code:
com_packtpub_store_Store.cpp
, declare method descriptors with type jmethodID
for each callback, which is going to be cached:... static Store gStore; static jclass StringClass; static jclass ColorClass; static jmethodID MethodOnSuccessInt; static jmethodID MethodOnSuccessString; static jmethodID MethodOnSuccessColor; ...
JNI_OnLoad()
. This can be done in two main steps:Getting a Class descriptor with the JNI method FindClass()
. One can find a class descriptor, thanks to its absolute package path, here: com./packtpub/store/Store
.
Retrieving a method descriptor from the class descriptor with GetMethodID()
. To differentiate several overloaded methods, the signatures retrieved earlier with javap
must be specified:
... JNIEXPORT jint JNI_OnLoad(JavaVM* pVM, void* reserved) { JNIEnv *env; if (pVM->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { abort(); } ... // Caches methods. jclass StoreClass = env->FindClass("com/packtpub/store/Store"); if (StoreClass == NULL) abort(); MethodOnSuccessInt = env->GetMethodID(StoreClass, "onSuccess", "(I)V"); if (MethodOnSuccessInt == NULL) abort(); MethodOnSuccessString = env->GetMethodID(StoreClass, "onSuccess", "(Ljava/lang/String;)V"); if (MethodOnSuccessString == NULL) abort(); MethodOnSuccessColor = env->GetMethodID(StoreClass, "onSuccess", "(Lcom/packtpub/store/Color;)V"); if (MethodOnSuccessColor == NULL) abort(); env->DeleteLocalRef(StoreClass); // Store initialization. gStore.mLength = 0; return JNI_VERSION_1_6; } ...
pThis
) when an integer is successfully inserted in setInteger()
. To invoke a Java method on a Java object, simply use CallVoidMethod()
(which means that the called Java method returns void). To do so, we need:... JNIEXPORT void JNICALL Java_com_packtpub_store_Store_setInteger (JNIEnv* pEnv, jobject pThis, jstring pKey, jint pInteger) { StoreEntry* entry = allocateEntry(pEnv, &gStore, pKey); if (entry != NULL) { entry->mType = StoreType_Integer; entry->mValue.mInteger = pInteger; pEnv->CallVoidMethod(pThis, MethodOnSuccessInt, (jint) entry->mValue.mInteger); } } ...
... JNIEXPORT void JNICALL Java_com_packtpub_store_Store_setString (JNIEnv* pEnv, jobject pThis, jstring pKey, jstring pString) { // Turns the Java string into a temporary C string. StoreEntry* entry = allocateEntry(pEnv, &gStore, pKey); if (entry != NULL) { entry->mType = StoreType_String; ... pEnv->CallVoidMethod(pThis, MethodOnSuccessString, (jstring) pEnv->NewStringUTF(entry->mValue.mString)); } } ...
... JNIEXPORT void JNICALL Java_com_packtpub_store_Store_setColor (JNIEnv* pEnv, jobject pThis, jstring pKey, jobject pColor) { // Save the Color reference in the store. StoreEntry* entry = allocateEntry(pEnv, &gStore, pKey); if (entry != NULL) { entry->mType = StoreType_Color; entry->mValue.mColor = pEnv->NewGlobalRef(pColor); pEnv->CallVoidMethod(pThis, MethodOnSuccessColor, (jstring) entry->mValue.mColor); } } ...
Launch the application and insert an integer, a string, or color entry. A successful message is displayed with the inserted value. The native code called the Java side thanks to the JNI Reflection API. This API is not only useful to execute a Java method, it is also the only way to process jobject
parameters passed to a native method. However, if calling C/C++ code from Java is rather easy, performing Java operations from C/C++ is a bit more involving!
Although a bit repetitive and verbose, calling any Java method should always be as trivial as this:
Store
Java object):jclass StoreClass = env->FindClass("com/packtpub/store/Store");
Method
class in Java). These method descriptors are retrieved from the class descriptor, which owns it (like a Class
in Java):jmethodID MethodOnSuccessInt = env->GetMethodID(StoreClass, "onSuccess", "(I)V");
JNI_OnLoad()
makes it easy to cache JNI descriptors before any native call is made. Descriptors whose names end with Id
, such as jmethodID
, can be freely cached. They are not references that can be leaked, or have to be made global on the opposite to jclass
descriptors.env->CallVoidMethod(pThis, MethodOnSuccessInt, (jint) myInt);
Whatever method you need to call on a Java object, the same process always applies.
Once you know the Reflection API, you know most of the JNI. Here are some of the provided methods that may be useful:
FindClass()
retrieves a (Local) reference to a Class
descriptor object according to its absolute path:jclass FindClass(const char* name)
GetObjectClass()
has the same purpose, except that FindClass()
finds class definitions according to their absolute path, whereas the other finds the class directly from an object instance (such as getClass()
in Java):jclass GetObjectClass(jobject obj)
<init>
and they have a void return value:jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig) jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
jobject GetObjectField(jobject obj, jfieldID fieldID) <primitive> Get<Primitive>Field(jobject obj, jfieldID fieldID) void SetObjectField(jobject obj, jfieldID fieldID, jobject value) void Set<Primitive>Field(jobject obj, jfieldID fieldID, <jprimitive> value)
jobject CallObjectMethod(JNIEnv*, jobject, jmethodID, ...) <jprimitive> Call<Primitive>Method(JNIEnv*, jobject, jmethodID, ...);
A
and V
postfix. The behavior is identical, except that arguments are specified respectively using a va_list
(that is, variable argument list) or jvalue
array (jvalue
being a union of all JNI types):jobject CallObjectMethodV(JNIEnv*, jobject, jmethodID, va_list); jobject CallObjectMethodA(JNIEnv*, jobject, jmethodID, jvalue*);
Have a look at jni.h
in the Android NDK include
directory to see all the possibilities by the JNI reflective API.
The goal of JNI calls is often performance. Thus, JNI does not perform advanced checking when its API methods are invoked. Hopefully, there exists an extended checking mode, which performs advanced checks and gives feedback in the Android Logcat.
To activate it, run the following command from a command prompt:
adb shell setprop debug.checkjni 1
The extended checking mode is available for applications started after this flag is set, until it is set to 0
, or until the device is rebooted. For rooted devices, the whole device can be started with this mode with the following commands:
adb shell stop adb shell setprop dalvik.vm.checkjni true adb shell start
If everything works properly, a message Late-enabling – Xcheck:jni appears in the Logcat when your application starts. Then, check the Logcat regularly to find its JNI warning or error.
Parallel programming is a mainstream subject nowadays. Android makes no exception since the introduction of multicore processors. You can do the threading entirely on the Java side (with the Java Thread and Concurrency APIs), on the native side (with the POSIX PThread API, which is provided by the NDK), and, more interestingly, between the Java and native side using JNI.
In this part, we will create a background thread, the watcher, which keeps a constant eye on what is inside the data store. It iterates through all entries and then sleeps for a fixed amount of time. When the watcher thread finds a key of a specific type predefined in the code, it acts accordingly. For this first part, we are just going to clip integer values to a predefined range.
Of course, threads need synchronization. The native thread is going to access and update the store only when a user understands the UI thread, and does not modify it. The native thread is created in C/C++ but the UI thread is a Java thread. We are going to use JNI monitors to synchronize both of them.
3.138.174.195