Caching jfieldID, jmethodID, and referencing data to improve performance

This recipe covers caching in Android JNI, which can improve the performance of our native code.

Getting ready

You should make sure you've read the following recipes before going through this recipe:

  • Accessing Java static and instance fields in native code
  • Calling static and instance methods from native code

How to do it…

The following steps detail how to build a sample Android application that demonstrates caching in JNI:

  1. Create a project named 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.
  2. Create a file named cachingtest.c under the jni folder, then implement the InitIDs, CachingFieldMethodIDDemo1, CachingFieldMethodIDDemo2, and CachingReferencesDemo methods.
  3. Modify the CachingActivity.java file by adding code to load the native library, then declare and invoke the native methods.
  4. Modify the layout XML file, add the 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.
  5. Run the project on an Android device or emulator and monitor the logcat output with either eclipse or the adb logcat -v time command in your terminal.
  6. At the 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:
    How to do it…
  7. Enable 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:
    How to do it…
  8. Enable 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:
    How to do it…

How it works…

This recipe discusses the usage of caching at JNI:

  • Caching field and method IDs: Field and method IDs are internal pointers. They're required to access a Java field or making native to Java method calls. Obtaining the field or method ID requires calling pre-defined JNI functions, which do symbolic lookups according to the name and descriptor. The lookup process typically requires several string comparisons and is relatively expensive.

    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.

  • Caching references: JNI exposes classes, instance objects, strings, and arrays as references. We covered how to manage references in the Managing references at JNI recipe. Sometimes, caching references can also improve performance. Unlike field and method IDs, which are direct pointers, references are implemented using an indirect mechanism that is hidden from developers. Therefore, we need to rely on JNI functions to cache them.

    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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset