JNI exposes strings, classes, instance objects, and arrays as reference types. This recipe will discuss arrays in JNI.
You should make sure you've read the following recipes before going through this recipe:
In this section, we will create a sample Android project that demonstrates how to manipulate arrays in JNI.
ArrayManipulation
. Set the package name as cookbook.chapter2
. Create an activity named ArrayManipulationActivity
. 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.arraytest.c
under the jni
folder, then implement the GetArrayLengthDemo
, NewObjectArrayDemo
, NewIntArrayDemo
, GetSetObjectArrayDemo
, GetReleaseIntArrayDemo
, GetSetIntArrayRegionDemo
, and GetReleasePrimitiveArrayCriticalDemo
native methods.ArrayManipulationActivity.java
by adding code to load the native library, declare the native methods, and invoke them.Dummy
class with a single integer field named value
.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.Arrays are represented by jarray
or its subtypes such as jobjectArray
and jbooleanArray
. Similar to jstring
, they cannot be accessed directly by native code like C arrays do. JNI provides various functions for accessing arrays:
NewObjectArray
and New<Type>Array
functions to create arrays for objects and primitive types. Their function prototypes are as follows:jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementType, jobject initialElement); <ArrayType> New<Type>Array(JNIEnv *env, jsize length);
We demonstrate the usage of NewObjectArray in the native method NewObjectArrayDemo
, where we create 10
instances of the Dummy
class. The length
parameter of the function indicates the number of objects to create, elementType
is a reference to the class, and initialElement
is the initialization value that is going to be set for all the created object instances in the array. In the Java code, we implemented the
callNewObjectArrayDemo
method, which calls the NewObjectArrayDemo
native method to create an array of 10 Dummy
objects, all with the value field set to 5
. The execution result should look similar to the following screenshot:
As expected, the value
field of all the objects created by NewObjectArray
is 5
.
The usage of New<Type>Array
is shown in the native method NewIntArrayDemo
, where we create an array of 10 integers using the JNI function NewIntArray
, and then assign a value to each of the integers. All eight primitive types (jboolean
, jbyte
, jchar
, jshort
, jint
, jlong
, jfloat
, and jdouble
) of JNI have a corresponding New<Type>Array
function to create an array of its type. Note that NewIntArrayDemo
calls the GetIntArrayElements
and ReleaseIntArrayElements
JNI functions, which we'll discuss later in this recipe. In the Java code, we implemented a callNewIntArrayDemo
method to call NewIntArrayDemo
and
display the integer array elements on the screen. The execution of callNewIntArrayDemo
gives the following result:
As shown in the screenshot, the integer arrays are assigned with values from 0
to 9
.
GetArrayLength
: This native function has the following prototype:jsize GetArrayLength(JNIEnv *env, jarray array);
It accepts a reference to jarray
and returns its length. We demonstrated its usage in the native method GetArrayLengthDemo
. In the Java code, we implemented the
callGetArrayLengthDemo
method, which creates three arrays, including a double
array, a
Dummy
object array, and a two-dimensional array of integers. The method calls the GetArrayLengthDemo
native method to find the lengths for the three arrays. We output the array
length to logcat in the native method. The sample execution output should look similar to the following screenshot:
GetObjectArrayElement
and SetObjectArrayElement
. As its name suggests, the first one retrieves a reference to an object element of an array, while the second one sets the element of an object array. The two functions have the following prototype:jobject GetObjectArrayElement(JNIEnv *env,jobjectArray array, jsize index); void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
In the two functions, the argument array
refers to the object array and index
is the position of the element. While the get
function returns a reference to the object element, the set
function sets the element according to the value
argument.
We illustrate the usage of the two functions in native method GetSetObjectArrayDemo
. The method accepts an object array and an object. It replaces the object at index one with the object received and then returns the original object at index one. In the Java code, we call the callGetSetObjectArrayDemo
method to pass an array of three Dummy
objects with values of 0
, 1
, 2
, and another Dummy
object of value 100
to the native method. The execution
result should look similar to the following screenshot:
As shown, the object at index 1
is replaced by the object with value 100
, and the original object of value 1
is returned.
jintarray
as an example. Arrays of other primitive types are similar to integers.Firstly, if we want to create a separate copy of jintarray
in a native buffer, or only access a small portion of a large array, GetIntArrayRegion
/
SetIntArrayRegion
functions are the proper choices. These two functions have the following prototype:
void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint* buf); void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint* buf);
The two functions accept the same set of input parameters. The argument array
refers to the jintArray
we operate on, start
is the start element position, len
indicates the number of elements to get or set, and buf
is the native integer buffer. We show the usage of these two functions in a native method called GetSetIntArrayRegionDemo
. The method accepts an input jintArray
, copies three elements from index 1 to 3 of the array to a native buffer, multiplies their values by 2
at the native buffer, and copies the value back to index 0
to 2
.
In the Java code, we implement the
callGetSetIntArrayRegionDemo
method to initialize an integer array, pass the array to a native method GetSetIntArrayRegionDemo
, and display the before and after values of all the elements. You should
see an output similar to the following screenshot:
The initial values for the five elements were 0
, 1
, 2
, 3
, and 4
. We copied three elements from index one (1
, 2
, 3
) to the native buffer buf
. We then multiplied the values at the native buffer by 2
, which made the first three elements at the native buffer 2
, 4
, and 6
. We copied these three values from the native buffer back to the integer array, starting at index 0
. The final values for the three elements were therefore 2
, 4
, and 6
, and the last two elements remained unchanged as 3
and 4
.
Secondly, if we want to access a large array, then GetIntArrayElements
and ReleaseIntArrayElements
are the JNI functions for us. They have the following prototype:
jint *GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy); void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode);
GetIntArrayElements
returns a pointer to the array elements, or NULL
in case of a failure. The array input parameter refers to the array we want to access, and isCopy
is set to true
if a new copy is created after the function call finishes. The returned pointer is valid until ReleaseIntArrayElements
is called.
ReleaseIntArrayElements
informs the VM that we don't need access to the array elements any more. The input parameter array
refers to the array we operate on, elems
is the pointer returned by GetIntArrayElements
, and mode
indicates the release mode. When isCopy
at GetIntArrayElements
is set to JNI_TRUE
, the changes we make through the returned pointer will be reflected on the jintArray
, since we're operating on the same copy. When isCopy
is set to JNI_FALSE
, the mode
parameter determines
how the data release is done. Depending upon whether we want to copy values from the native buffer back to the original array, and whether we want to free the elems
native buffer, the mode
parameters can be 0
, JNI_COMMIT
, or JNI_ABORT
, as follows:
Copy values back |
Yes |
No |
---|---|---|
Free native buffer | ||
Yes |
|
|
No |
JNI_ |
- |
We illustrate the two JNI functions with the native method GetReleaseIntArrayDemo
. The method accepts an input integer array, obtains a native pointer through GetIntArrayElements
, multiplies each element by 2
, and finally commits the changes back by ReleaseIntArrayElements
with mode
set to 0
. In the Java code, we implement the callGetReleaseIntArrayDemo
method to initialize the input array and invoke the GetReleaseIntArrayDemo
native method. The following is a screenshot of the phone display after executing the callGetReleaseIntArrayDemo
method:
As expected, all integer elements in the original array are multiplied by 2
.
The third set of JNI functions are GetPrimitiveArrayCritical
and ReleasePrimitiveArrayCritical
. The usage of these two functions is similar to that of Get<Type>ArrayElements
and Release<Type>ArrayElements
, except for one important difference—the code block between the Get
and Release
methods is a critical region. No other JNI functions or function calls causing the current thread to wait for another thread in the same
VM shall be made. These two methods essentially increase the possibility of obtaining an uncopied version of the original primitive array, and therefore improve the performance. We demonstrate the usage of these functions in a native method GetReleasePrimitiveArrayCriticalDemo
along with the Java method callGetReleasePrimitiveArrayCriticalDemo
. The implementations are similar to the second set of functions calls, and the display result is the same.