This recipe covers caching in Android JNI, which can improve the performance of our native code.
You should make sure you've read the following recipes before going through this recipe:
The following steps detail how to build a sample Android application that demonstrates caching in JNI:
Caching
. Set the package name as cookbook.chapter2
. Create an activity named CachingActivity
. Under the project, create a folder named jni
. Refer to the Loading native libraries and registering native methods recipe of this chapter for more detailed instructions.cachingtest.c
under the jni
folder, then implement the InitIDs
, CachingFieldMethodIDDemo1
, CachingFieldMethodIDDemo2
, and CachingReferencesDemo
methods.CachingActivity.java
file by adding code to load the native library, then declare and invoke the native methods.Android.mk
build file, and build the native library. Refer to steps 8 to 10 of the Loading native libraries and registering native methods recipe of this chapter for details.adb logcat -v time
command in your terminal.onCreate
method of CachingActivity.java
, enable the callCachingFieldMethodIDDemo1
method and disable the other demo methods. Start the Android application, and you should be able to see the following at logcat:callCachingFieldMethodIDDemo2
at CachingActivity.java
while disabling the other demo methods and InitIDs
method (at the static initializer). Start the Android application, and you should be able to see the following at logcat:callCachingReferencesDemo
at CachingActivity.java
while commenting out other demo methods. Start the Android application, and you should be able to see the following at logcat:This recipe discusses the usage of caching at JNI:
Once the field or method ID is obtained, accessing the field or making native to Java calls is relatively quick. Therefore, a good practice is to perform lookup only once and cache the field or method ID.
There are two approaches to cache field and method IDs. The first approach caches at the class initializer. In Java, we can have something similar to the following:
private native static void InitIDs(); static { System.loadLibrary(<native lib>); InitIDs(); }
The static initializer is guaranteed to be executed before any of the class's methods. Therefore, we can ensure that the IDs required by the native method are valid when they're invoked. The usage of this approach is demonstrated in the InitIDs
and CachingFieldMethodIDDemo1
native methods and CachingActivity.java
.
The second approach caches the IDs at the point of usage. We store the field or method ID in a static variable, so that the ID is valid the next time the native method is invoked. The usage of this approach is demonstrated in the native methods CachingFieldMethodIDDemo2
and CachingActivity.java
.
On comparison of these two approaches, the first one is preferred. Firstly, the first it doesn't require a validity check for the IDs before using them, because the static initializer is always called first and the IDs are therefore always valid before the native methods are called. Secondly, if the class is unloaded, the cached IDs will be invalid. If the second approach is used, we'll need to ensure the class is not unloaded and loaded again. If the first approach is used, the static initializer is called automatically when the class is loaded again, so we never have to worry about the class being unloaded and loaded again.
In order to cache reference data, we need to make it a global reference or weak global reference. A global reference guarantees that the reference will be valid until it is explicitly deleted. While weak global reference allows the underlying object to be garbage collected. Therefore, we'll need to do a validity check before using it.
The native method CachingReferencesDemo
demonstrates how to cache a string reference. Note that while DeleteGlobalRef
makes the global reference invalid, it doesn't assign NULL
to the reference. We'll need to do this manually.