[Android] Programming Objects between Java and JNI

Sometimes, you have to implement the performance sensitivity or low-level operations in your application. That means you need to execute the code on the native level rather than the side of Java Virtual Machine. In that case, we may transfer the objects  between Java side and JNI side. So, in order to access the data in the different sides, you should know how to use “Field” in the JNI.
There are many examples in the Android source code. Some good materials are placed in the “ANDROID_TOP_DIR/framework/base/core/jni”. They are released by Google and used in the Android System. So, you can trace the code for learning.
Here I will introduce how to transfer the Java object into JNI (C/C++ side) or to return a new Java object from JNI to Java side.

1. First, we should define the Params class that be set into JNI side.

 
public class Params {
public int mWidth;
public int mHeight;

public Params(int width, int height) {
mWidth = width;
mHeight = height;
}
}

2. Some helper functions and declaration should be defined in the JNI.

 
/// Help to get the field ID.
struct field {
const char *class_name;
const char *field_name;
const char *field_type;
jfieldID *jfield;
};

/// Define all the fields
struct fields_t {
jfieldID width;
jfieldID height;

jfieldID rectBottom;
jfieldID rectLeft;
jfieldID rectRight;
jfieldID rectTop;
} fields;

/// A function helps you to get the all the field IDs
static int find_fields(JNIEnv *env, field *fields, int count)
{
for (int i = 0; i < count; i++) {
field *f = &fields[i];
jclass clazz = env->FindClass(f->class_name);
if (clazz == NULL) {
LOGE("Can't find %s", f->class_name);
return -1;
}

jfieldID field = env->GetFieldID(clazz, f->field_name, f->field_type);
if (field == NULL) {
LOGE("Can't find %s.%s", f->class_name, f->field_name);
return -1;
}

*(f->jfield) = field;
}

return 0;
}

/* This function will be call when the library first be loaded */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
JNIEnv* env = NULL;
LOGI(TAG, "JNI_OnLoad!");

if (vm->GetEnv((void**) &uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
LOGE(TAG, "ERROR: GetEnv failed");
return -1;
}

env = uenv.env;;

/// An array for collecting all the fields
field fields_to_find[] = {
{ "com/example/Params", "mWidth", "I", &fields.width },
{ "com/example/Params", "mHeight", "I", &fields.height },

{ "android/graphics/Rect", "bottom", "I", &fields.rectBottom },
{ "android/graphics/Rect", "left", "I", &fields.rectLeft },
{ "android/graphics/Rect", "right", "I", &fields.rectRight },
{ "android/graphics/Rect", "top", "I", &fields.rectTop },
};

/// Find all the field IDs
if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0)
return -1;

return JNI_VERSION_1_6;
}

3. How to Return the Object Modification from JNI to Java?

 
JNIEXPORT void JNICALL
Java_com_example_JNItest1(JNIEnv* env, jobject thiz, jobject _params)
{
/* Get all the fields */
const int width = (int) env->GetIntField(_params, fields.width);
const int height = (int) env->GetIntField(_params, fields.height);
}

You should use the Get<Type>Field method to get the values from _params object. In this case, the _params object contains two members which are the width and height. Follow the declaration of GetIntField, is like this,

jint GetIntField(Object obj, jfieldID fieldID)

The obj includes all the members you want to access, so we give it  the _params. Another one is fieldID, this is the field of class that you define it in fields_to_find structure. And you got the ID of the field by invoking the find_field function before.

4. How to Return the New Object from JNI to Java?
Sometimes we do some operations in the JNI side, and then we should return the result by a type of object. The one approach is like previous method. You can replace the GetIntField by SetIntField method. The modified values can be seen in the Java side.
But in here, I will create a new object for storing these results. The approach provides a chance that programmer can instance the Java class and use the it in the JNI. Finally, we just need to return the object, the Java side can receive the returned object.

Here we use the android.graphics.Rect class to explain.

 
public final class Rect implements Parcelable {
public int left;
public int top;
public int right;
public int bottom;

....
}

You can found out the source code of Rect class in Android framework. Here is the Java native method  that we are called,

 
public static native ArrayList<Rect> JNItest2(Parameters params);

The following code shows how to instance the Java class and to use it in the JNI.

 
JNIEXPORT jobject JNICALL
Java_com_example_JNItest2(JNIEnv* env, jobject thiz)
{
/// Just for adding the multiple elements into arraylist
int objIndex;
const int endIndex = 10;

/// Part1: java.util.ArrayList
jclass clsArrayList = env->FindClass("java/util/ArrayList");
jmethodID constructor = env->GetMethodID(clsArrayList, "<init>", "()V");
jobject objArrayList = env->NewObject(clsArrayList, constructor, "");
jmethodID arrayListAdd = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");

/// Part2: android.graphics.Rect
jclass clsRect = env->FindClass("android/graphics/Rect");
jmethodID constructorRect = env->GetMethodID(clsRect, "<init>", "()V");

for (objIndex = 0; objIndex < endIndex; ++objIndex) {
/// Part3: Create the new rect and add it into ArrayList
jobject objRect = env->NewObject(clsRect, constructorRect, "");
const int bottom = 1;
const int left = 2;
const int right = 3;
const int top = 4;

env->SetIntField(objRect, fields.rectBottom, bottom);
env->SetIntField(objRect, fields.rectLeft, left);
env->SetIntField(objRect, fields.rectRight, right);
env->SetIntField(objRect, fields.rectTop, top);

env->CallObjectMethod(objArrayList, arrayListAdd, objRect);
}

env->DeleteLocalRef(clsArrayList);
env->DeleteLocalRef(clsRect);

return objArrayList;
}

There are three parts in the above code,
Part1:
First, we should create the ArrayList object for storing the instance of Rect class. In here, the key point is we obtain the method ID of constructor and add method by the GetMethodID function. The two methods will be used later.

Part2:
The elements should be created with the type of Rect, so we should find the Rect class and the method ID before creating objects.

Part3: 
In the for-loop, we create a Rect object by the constructor of Rect class. Then, we need to call the Set<Type>Field functions to put the values into the object. Finally, we call the add method in the ArrayList. The Rect object shall be put into the ArrayList.

5. Conclusion:
In this article, the two JNI examples can help you to understand how to transfer the information between Java and JNI. You should know some functions such as FindClass, Get<Type>Field, Set<Type>Field. NewObject, and GetMethodID. The Java Native Interface: Programmer’s Guide and Specification can help you to know the detail of these functions. In the JNI programming, you should handle the memory leak issue carefully. You can obtain the information from the topic about Local Reference and Global Reference.

– 2013/08/27 Updated:
Thanks the comment from “JeeWoong Park“. I have been modified the code that in part 3 from “env->CallObjectMethod(objArrayList, arrayListAdd, objFace);” to “env->CallObjectMethod(objArrayList, arrayListAdd, objRect);“.


11 comments

  1. good example but i want to know how to copy one array list some element to anther array list in jni???

  2. did you know? How can I add element on particular position in objarraylist??

  3. jclass clsArrayList = env->FindClass("java/util/ArrayList");

    jmethodID arrayListAdd = env->GetMethodID(clsArrayList, "add", "(ILjava/lang/Object;)V");
    env->CallObjectMethod(objArrayList, arrayListAdd, index, insertedObj);

  4. Thaks for your replay but I got following error

    09-18 10:45:36.636: W/dalvikvm(4155): JNI WARNING: expected return type 'L'
    09-18 10:45:36.636: W/dalvikvm(4155): calling Ljava/util/ArrayList;.add (ILjava/lang/Object;)V
    09-18 10:45:36.636: W/dalvikvm(4155): in Lcom/example/imagelibrary/HelloNDK;.InsertJPEGObject:(Ljava/util/ArrayList;IILjava/lang/String;IIILjava/lang/String;)Ljava/util/ArrayList; (CallObjectMethodV)

  5. I forgot to change the CallMethod. For this case, you need to use the "CallVoidMethod".

  6. Last code block, I think you should replace the line

    env->CallObjectMethod(objArrayList, arrayListAdd, objRect);

    with

    env->CallBooleanMethod(objArrayList, arrayListAdd, objRect);

發佈留言