The previous recipe discusses that Android JNI supports three different kinds of references. The references are used to access the reference data types, including string, class, instance object, and array. This recipe focuses on class manipulations in Android JNI.
The Managing References in NDK recipe should be read first before going through this recipe.
The following steps describe how to build a sample Android application that illustrates class manipulation in JNI:
ClassManipulation
. Set the package name as cookbook.chapter2
. Create an activity named ClassManipulationActivity
. Under the project, create a folder named jni
. Refer to the Loading native libraries and registering native methods recipe of this chapter if you want more detailed instructions.classtest.c
under the jni
folder, then implement the findClassDemo
, findClassDemo2
, GetSuperclassDemo
, and IsAssignableFromDemo
methods. We can refer to the downloaded ClassManipulation
project source code.ClassManipulationActivity.java
by adding code to load the native library, declare native methods, and invoke native methods.Dummy
class and a DummySubClass
subclass that extends the Dummy
class. Create a DummyInterface
interface and a DummySubInterface
subinterface, which extends the DummyInterface
.layout
XML file, add the Android.mk
build file, and build the native library. Refer to steps 8 to 10 of the the Loading native libraries and registering native methods recipe of this chapter for details.This recipe demonstrates the manipulation of classes in JNI. We highlight a few points as follows:
.
" character in Java with "/
" in JNI programming. For example, the descriptor for class java.lang.String
is java/lang/String
.FindClass
has the following prototype:jclass FindClass(JNIEnv *env, const char *name);
It accepts a
JNIEnv
pointer and a class descriptor, and then locates a class loader to load the corresponding class. It returns a local reference to an initialized class, or NULL
in case of failure. FindClass
uses the class loader associated with the topmost method of the call stack. If it cannot find one, it will use the "system" class loader. One typical example is that after we create a thread and attach it to the VM, the topmost method of the call stack will be as follows:
dalvik.system.NativeStart.run(Native method)
This method is not part of our application code. Therefore the "system" class loader is used.
A thread can be created at Java (called the managed thread or Java thread) or native code (called the native thread or non-VM thread). The native thread can be attached to a VM by calling the JNI function AttachCurrentThread
. Once attached, the native thread works just like a Java thread, running inside a native method. It remains attached until the JNI function DetachCurrentThread
is called.
In our ClassManipulation
project, we illustrated FindClass
with findClassDemo
and findClassDemo2
native methods. The
findClassDemo
method runs in a VM created thread. The
FindClass
call will locate the class loader properly. The findClassDemo2
method creates a non-VM thread and attaches the thread to
VM. It illustrates the case we described in the preceding section. The logcat output for calling the two native methods is as follows:
As shown in the output, the non-VM thread loads the String
class successfully but not the
Dummy
class defined by us. The way to work around this issue is to cache a reference to the Dummy
class in the
JNI_OnLoad
method. We'll provide a
detailed example in the Caching jfieldID, jmethodID, and referencing data to improve performance recipe.
GetSuperclass
: The JNI function GetSuperclass
has the following prototype:jclass GetSuperclass(JNIEnv *env, jclass clazz);
It helps us to find the superclass of a given class. If clazz
is java.lang.Object
, this function returns NULL
; if it's an interface, it returns a local reference to java.lang.Object
; if it's any other class, it returns a local reference to its superclass.
In our ClassManipulation
project, we illustrated GetSuperclass
with the GetSuperclassDemo
native method. We created a Dummy
class and a DummyInterface
interface in Java code, where DummySubClass
extends Dummy
, and DummySubInterface
extends DummyInterface
. In the native method, we then invoked GetSuperclass
to java.lang.Object
, DummySubClass
, and DummySubInterface
respectively. The following is a screenshot of the logcat output:
As shown in the screenshot, GetSuperclass
can find the superclass of DummySubClass
successfully. In this native method, we used a utility function nativeGetClassName
, where we called the
toString
method. We'll cover more about how to make such method calls in the Calling instance and static methods in JNI recipe.
IsAssignableFrom
: The JNI function IsAssignableFrom
has the following prototype:jboolean IsAssignableFrom(JNIEnv *env, jclass cls1, jclass cls2);
This function returns JNI_TRUE
if cls1
can be safely casted to cls2
, and JNI_FALSE
otherwise. We demonstrated its usage with the native method IsAssignableFromDemo
. We obtained a local reference to DummySubClass
, and called GetSuperclass
to get a local reference to Dummy
. Then, we called
IsAssignableFrom
to test if we can cast DummySubClass
to Dummy
and vice versa. The following is a screenshot of the logcat output:
As expected, the subclass can be safely cast to its superclass, but not the other way round.