Lab archive: lab-6.zip Lab solutions: lab-6-solved.zip
To access a Java array in the JNI you need a reference to it. You can pass the reference from Java or create a new array inside the JNI. Arrays have the type: j<type>Array, for example jintArray.
To create a Java array from JNI:
jintArray javaArray = env->NewIntArray(jsize length);
or
jobjectArray javaArray = env->NewObjectArray(jsize length, jclass elementClass, jobject initialElement);
where length is the number of elements in the array, elementClass is the class of the Objects and initialElement is the element each item in the array will be initialized as.
To access a primitive type array use:
env->Get<Type>ArrayRegion(j<type>Array array, jsize start, jsize len, j<type> *buf);
This will copy len elements, starting at element start from array into buf (starting from position 0 in buf).
To copy back from a native array use:
env->Set<Type>ArrayRegion(j<type>Array array, jsize start, jsize len, j<type> *buf);
This will copy len elements, starting at element start from buf into array (starting from position 0 in buf).
For object arrays, use
jobject obj = env->GetObjectArrayElement(jobjectArray array, jsize index); env->SetObjectArrayElement(jobjectArray array, jsize index, jobject obj);
instead. You cannot access more than one object at once (and it is impractical since there is a small amount of object references you can have at any one time).
Implement the two native functions: getIntArray and getStringArray. Allocate the arrays and initialize the elements to 1, 2, 3, 4, 5 and “one”, “two”, “three”, “four”, “five” respectively (from native code). They will get printed to the log. Use NewStringUTF to create the Strings. Return the created arrays. Use DeleteLocalRef to remove the String references (JNI guarantees only 16 references to Java objects at any one time).
In the getIntArray method, after creating the array, use
globalObj = env->NewGlobalRef(jobject obj)
to create a global reference. Make sure globalObj is declared globally. This means that globalObj will remain valid until it is deleted.
DeleteGlobalRef(globalObj);
deletes a global reference.
There are also Weak Global references, which allow objects to be garbage collected. You have to check if the reference is still valid before using it with IsSameObject.
Implement the updateArray method. Use the global reference to get access to the array. Increment each element by one. Use GetArrayLength to get the length of the array.
Get<Type>ArrayRegion and Set<Type>ArrayRegion copy the array each time they are used. This will add overhead. Use
j<type> *directArray = env->Get<Type>ArrayElements(j<type>Array array, jboolean *isCopy)
to get a direct reference (if possible).
To release the array and free space
env->Release<Type>ArrayElements(j<type>Array array, j<type> *directArray, jint mode)
where can be:
Copying is only done if directArray is a copy.
Modify the updateArray method to use direct copies of the array if possible.
To synchronize access to objects, Java uses the synchronized key word
synchronized(obj) { // Thread safe obj access }
To synchronize native threads and Java threads, JNI has the MonitorEnter and MonitorExit.
if (env->MonitorEnter(jobject obj) == 0) { // ThreadSafe if (env->MonitorExit(jobject obj) != 0) { //Error } } else { // Error }
Synchronize access to the array used in updateArray.
Implement the getBuffer method. It receives as a parameter the number of bytes to allocate, and then it should allocate that number of bytes and return a ByteBuffer. To create a byte buffer use:
jobject directBuffer = env->NewDirectByteBuffer(unsigned char* buffer, jsize length)
This allows you to share access to the bytes directly between the native code and Java. Keep a global reference to buffer. Remember, you have to free this space you allocate yourself, Java will not clean it.
To get the native buffer back from Java, call:
unsigned char* buffer = env->NewDirectByteBuffer(jobject directBuffer)
If the ByteBuffer was allocated from Java code, then memory will be handled by Java.
In Android Java heap size is very limited, however, malloc or new is not counted towards the same heap size, so you can overcome that limitation by allocating space from C.
Implement the greyScale method. The Bitmap representation in the buffer will be RGBARGBARG..RGBA, the size being 4 * Width * Height, and scanning horizontally: the first line is the top one, the first pixel is top left.
Remember to flip the buffer after using it in Java.
One of the formulas for grey scale is: R,G,B = 0.21 R + 0.71 G + 0.07 B.
Implement the applyFilter method. Use two image filters from http://lodev.org/cgtutor/filtering.html. Create a new buffer (or reuse a previous one if you have already applied a filter) for each image, and apply the filter on top of the original image (or the greyscale one). Implement the functionality of the two buttons for filters. Make sure to run filters on a separate thread.
Put a image file on the SD card of the phone. Add READ_EXTERNAL_STORAGE permission. Read the file from the card and load it instead of the file from res/ folder (check the BitmapFactory class). Check if your filters and grey scale functions still work.