We have demonstrated how to pass parameters of different types to native methods and return data back to Java. This is not the only way of sharing data between the native code and Java code. This recipe covers another method—accessing Java fields from the native code.
We're going to cover how to access Java fields of different types, including primitive types, strings, instance objects, and arrays. The following recipes should be read first before reading this recipe:
Readers are also expected to be familiar with Java reflection API.
Follow these steps to create a sample Android project that demonstrates how to access Java static and instance fields from the native code:
AccessingFields
. Set the package name as cookbook.chapter2
. Create an activity named AccessingFieldsActivity
. 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.accessfield.c
under the jni
folder, then implement the AccessStaticFieldDemo
, AccessInstanceFieldDemo
, and FieldReflectionDemo
native methods.AccessingFieldsActivity.java
by adding code to load the native library, declare native methods, and invoke them. In addition, add four instance fields and four static fields.Dummy
class with an integer instance field named value
and an integer static field named value2
.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 discusses the access of fields (both static and instance fields) in Java from native code:
jfieldID
is a regular C pointer pointing to a data structure with details hidden from developers. We should not confuse it with jobject
or its subtypes. jobject
is a reference type corresponding to Object
in Java, while jfieldID
doesn't have such a corresponding type in Java. However, JNI provides functions to convert the java.lang.reflect.Field
instance to jfieldID
and vice versa.
Java field type |
Field descriptor |
---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
As shown in the table, each of the eight primitive types has a single character string as its field descriptor. For objects, the field descriptor starts with "L"
, followed by the class descriptor (refer to the Manipulating classes in JNI recipe for detailed information) and ends with ";
". For arrays, the field descriptor starts with "[
", followed by the descriptor for the element type.
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); <NativeType> GetStatic<Type>Field(JNIEnv *env,jclass clazz, jfieldID fieldID); void SetStatic<Type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID,<NativeType> value);
To access a static field, the first step is to obtain the field ID, which is done by the first function listed here. In the method prototype, the clazz
argument refers to the Java class at
which the static field is defined,
name
indicates the field name, and sig
is the field descriptor.
Once we have the method ID, we can either get or set the field value by calling function two or three. In the function prototype, <Type>
can refer to any of the eight Java primitive types or Object
, and fieldID
is jfieldID
returned by calling the first method. For set
functions, value
is the new value that we want to assign to the field.
The usage of the preceding three JNI functions are demonstrated in the native method AccessStaticFieldDemo
, where we set and get values for an integer field, a string field, an array field, and a Dummy
object field. These four fields are defined in the Java class AccessingFieldsActivity
. In native code, we output the get values to Android logcat, while in the Java code we display the value set by the native code to the phone screen. The
following screenshot shows the logcat output:
The phone display will look similar to the following screenshot:
As shown, the values we set at the Java code for the fields can be obtained by the native code; and the values set by the native method are reflected in the Java code.
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); <NativeType> Get<Type>Field(JNIEnv *env,jobject obj, jfieldID fieldID); void Set<Type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, <NativeType> value);
Again, we need to obtain the field ID first, before we can get and set the values for the field. Instead of passing the class reference to the get
and set
functions, we should pass the object reference.
The usage is shown in native method
AccessInstanceFieldDemo
. Again, we print the values of get
in the native code to the logcat and display the modified field values on the phone
screen. The following screenshot shows the logcat output:
The phone display will look similar to the following screenshot:
A similar interpretation to accessing static fields can be made on the results.
Field
. They have the following prototypes:jfieldID FromReflectedField(JNIEnv *env, jobject field); jobject ToReflectedField(JNIEnv *env, jclass cls, jfieldID fieldID, jboolean isStatic);
The first function converts java.lang.reflect.Field
to jfieldID
, and then we can use the set
and
get
JNI functions described previously. The argument field is an instance of java.lang.reflect.Field
.
The second function does the reverse. It accepts a class reference, a jfieldID
, and a jboolean
variable indicating whether it is a static or an instance field. The function returns a reference to an object of java.lang.reflect.Field
.
The usage of these two functions is demonstrated in the native method FieldReflectionDemo
. We used the Field
instance passed from the caller to access the field value, and then returned a Field
instance for another field. In the Java method callFieldReflectionDemo
, we pass the Field
instance to the native code and use the returned Field
instance to
obtain the field
value. The native code outputs the field value to logcat as follows:
The Java code displays the value for another field on the phone screen as follows: