The previous recipe covers how to access Java fields in NDK. Besides fields, a Java class also has methods. This recipe focuses on calling static and instance methods from JNI.
The code examples require a basic understanding of the JNI primitive types, strings, classes, and instance objects. It is better to make sure you have read the following recipes before going through this recipe:
Readers are also expected to be familiar with Java reflection API.
The following steps can be followed to create a sample Android project that illustrates how to call static and instance methods from the native code:
CallingMethods
. Set the package name as cookbook.chapter2
. Create an activity named CallingMethodsActivity
. 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.callmethod.c
under the jni
folder, then implement the native methods AccessStaticMethodDemo
, AccessInstanceMethodDemo
, and MethodReflectionDemo
.CallingMethodsActivity.java
by adding code to load the native library, declare the native methods, and invoke them.Dummy
class with an integer instance field named value
and an integer static field named value2
. In addition, create a DummySub
class that extends Dummy
with an additional String field called name
.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 more details.This recipe illustrates how to call the Java static and instance methods from the native code:
jfieldID
, jmethodID
is a regular C pointer pointing to a data structure with details hidden from the developers. JNI provides functions to convert the java.lang.reflect.Method
instance to jmethodID
and vice versa.()
", and appending the field descriptor of the return type. If the return type is void
, we should use "V
". If there's no input arguments, we should simply use "()
", followed by the field descriptor of the return type. For constructors, "V
" should be used to represent the return type. The following table lists a few Java methods and their corresponding method descriptors:jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); <NativeType> CallStatic<Type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...); <NativeType> CallStatic<Type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); <NativeType> CallStatic<Type>MethodV(JNIEnv *env, jclass clazz,jmethodID methodID, va_list args);
The first function gets the method ID. It accepts a reference clazz
to the Java class, a method name in a modified UTF-8 string format, and a method descriptor sig
. The other three sets of functions are used to call the static methods. <Type>
can be any of the eight primitive types, Void
, or Object
. It indicates the
return type of the method invoked. The
methodID
argument is the jmethodID
returned by the
GetStaticMethodID
function. The arguments to the Java method are passed one by one in CallStatic<Type>Method
, or put into an array of jvalue
as CallStatic<Type>MethodA
, or put into the va_list
structure as CallStatic<Type>MethodV
.
We illustrate the usage of all the four sets of JNI functions in a native method
AccessStaticMethodDemo
. This method gets the method IDs for the getValue2
and setValue2
static methods of the Dummy
class, and invokes these two methods using three different ways to pass the arguments to the called Java method. In CallingMethodsActivity.java
, we implement callAccessStaticMethodDemo
, which initializes the value2
static field to 100
, invokes the native method AccessStaticMethodDemo
, and prints the final value2
value on phone screen. The following screenshot shows the logcat output:
The output of screen looks similar to the following screenshot:
As shown, the native method firstly got value2
as 100
, and it then used three different JNI functions to call the set
method to modify the value. Finally, the phone display indicated that the final modified value is reflected in Java code.
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); <NativeType> Call<Type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...); <NativeType> Call<Type>MethodA(JNIEnv *env,jobject obj, jmethodID methodID, jvalue *args); <NativeType> Call<Type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
The usage of these four sets of functions is similar to that of the JNI functions for calling static methods, except that we need to pass a reference to the instance object instead of the class. In addition, JNI provides another three sets of functions for calling instance methods, as follows:
<NativeType> CallNonvirtual<Type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); <NativeType> CallNonvirtual<Type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, jvalue *args); <NativeType> CallNonvirtual<Type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);
These three sets of methods accept an extra argument clazz
as compared to the three sets of functions earlier. The
clazz
argument can be a reference to the class that obj
is instantiated from, or a superclass of obj
. A typical use case is to call GetMethodID
on a class to obtain a jmethodID
. We have a reference to an object of the class's subclass, and then we can use the preceding functions to call the Java method associated by jmethodID
with the
object reference.
The usage of all the seven sets of functions is illustrated in a native method AccessInstanceMethodDemo
. We used the first four sets of functions to call
getName
and
setName
methods of the DummySub
class with an object of it. We then used CallNonvirtual<Type>Method
to call the getValue
and
setValue
methods, which are defined in the Dummy
superclass. In CallingMethodsActivity.java
, we implemented the callAccessInstanceMethodDemo
method to invoke the AccessInstanceMethodDemo
native method. The following screenshot shows the logcat output:
As the results show, the getName
, setName
, getValue
, and setValue
methods are executed successfully.
jmethodID FromReflectedMethod(JNIEnv *env, jobject method); jobject ToReflectedMethod(JNIEnv *env, jclass cls, jmethodID methodID, jboolean isStatic);
The first function accepts a reference to the java.lang.reflect.Method
instance, and returns its corresponding jmethodID
. The returned jmethodID
value can then be used to call the associated Java method. The second function does the reverse. It accepts a reference to the Java class, jmethodID
, and jboolean
indicating whether it's a static method or not, and returns a reference to java.lang.reflect.Method
. The return value can be used in the Java code to access the corresponding method.
We illustrate these two JNI functions in native method MethodReflectionDemo
. In CallingMethodsActivity.java
, we implement the callMethodReflectionDemo
method to pass the java.lang.reflect.Method
object of getValue
to the native code, get the returned setValue java.lang.reflect.Method
object, and invoke the setValue
method with the returned object.
The native method outputs the return value of getValue
method to logcat as follows:
The Java code displays the getValue
method return values before and after invoking setValue
on the phone screen as follows:
As expected, the native code can access the getValue
method with the Method
object passed from the Java code, and the Java code can call the setValue
method with the Method
object
returned from the native method.