This is an old revision of the document!


05 - Android NDK

  • Description:
  • Practical part:

Lecture

Practical

Resources

If you do not have the Android NDK installed, you can downloand it and extract it from this link. Afterwards in Android Studio you must set the NDK path if it is not set already. The path can be set by right clicking on the project and selecting the Open Module Settings menu item and setting the Android NDK location with a path similar to /home/student/android-ndk-10re.

Files

Task 1 - Create a Native Project (1p)

Because NDK is not fully support on Android Studio yet, you will have to import the project from this archive: nativeapplication.zip You should use this whenever a new project is required. You can find more information about NDK integration in Android Studio at this link.

Create a new project.

Edit native.c and add the following function:

jstring Java_com_example_student_nativeapplication_MainActivity_getString(JNIEnv *env, jobject thiz)
{
	return (*env)->NewStringUTF(env, "Hello world from JNI!");
}

The equivalent C++ code is:

extern "C" {
	jstring Java_com_example_student_nativeapplication_MainActivity_getString(JNIEnv *env, jobject thiz);
}
 
jstring Java_com_example_student_nativeapplication_MainActivity_getString(JNIEnv *env, jobject thiz)
{
	return env->NewStringUTF("Hello world from JNI!");
}

C++ adds decorators to functions so the ART VM would not be able to locate them if not declared as extern “C”.

As you can see the function name is a bit special: it starts with Java and contains the full class name (including package) and ends with the name of the function. The first two parameters are always a pointer to a JNIEnv class (or structure in C) which is an interface to the virtual machine and a jobject which is a reference to the object this method is called from (or a jclass for static methods).

To call the function from Java you must do two things: load the library and declare the method as a function. Add the following lines to your MainActivity.

static {
	System.loadLibrary("native");
}
 
private native String getString();

Then, make sure your activity has a TextView (with an id to reference it), and at onCreate, set the TextView to the String returned by getString().

There is a more practical way of generating function prototypes: using javah or by automatically adding the functions using Android Studio. Declare all your native functions in the Java files first and then call javah to generate the headers.

For example:

javah -classpath <sdk>/platforms/android-23/android.jar:android_workspace/native/bin/classes/ com.example.student.nativeapplication.MainActivity

will generate com_example_student_nativeapplication_MainActivity.h which contains

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_native1_MainActivity */
 
#ifndef _Included_com_example_student_nativeapplication_MainActivity
#define _Included_com_example_student_nativeapplication_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_student_nativeapplication_MainActivity
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_student_nativeapplication_MainActivity_getString
  (JNIEnv *, jobject);
 
#ifdef __cplusplus
}
#endif
#endif

Task 2 - Logging from Native Code (1p)

To use logging from native code you will have to include <android/log.h>. Add this include to native.c. You will have to also link this module against liblog. This already done in the imported project (ldLibs += “log” in gradle.build).

Then, to print something, you need to use this macro:

__android_log_print(log level, string tag, printf format, variables according to format);

For example

__android_log_print(ANDROID_LOG_VERBOSE, "TAG1", "variable 1 = %ld", var1);

Instrument the function in native library to tell you that it has been called.

Task 3 - Call a Java method from native code (1p)

Download the lab archive and import the project into Android Studio (File > New > Import Project). Build and run the application. You will notice there are 8 buttons, each with a TextView associated. Each of them correspond to a task, in order. The tasks in the archive are numbered from 1 through 8, but they correspond to tasks 3 through 10.

For the first task you have to call the setText() method from native code. To do this, you have to call

env->CallVoidMethod(obj, mID);

where obj is the object you want to call the method on and mID is the method ID. The Void in the name suggests that the method returns void.

To get the method ID you have to call

jmethodID mID = env->GetMethodID(cls, "setText", "()V");

where cls is the class the method is part of, the first string is the method name and the second string is a method signature. In this case, the method takes no parameters ”()” and returns void “V”.

Finally, to get the class, call

jclass cls = env->FindClass("osp/lab5/tasks/MainActivity");

which has a single parameter, the class name and package, with the path divided by ”/”.

It is good practice to check for null when getting a class or methodID, and treating the error as required.

Task 4 - Call a Java method with a parameter from native code (1p)

For the second task you have to call setText(String) method from native code. This is similar to the above task execept the method signature has changed, instead of ()V, the new method signature will be (Ljava/lang/String;)V. All parameters must have ; after them if they are objects. Objects are defined as L followed by the fully qualified class. Check the JNI Types Documentation.

To set the parameter, create a new jstring, and give it as a parameter after the methodID.

jstring str = env->NewStringUTF("OK");
env->CallVoidMethod(obj, mID, str);

Remember to release the string after you are done with it.

env->ReleaseStringUTFChars(str, env->GetStringUTFChars(str, NULL));

Task 5 - Get the return value of a Java method (1p)

Call String getInput(). This method returns the content of the EditText box under of task 3. There are three things that need to change from the first task: the method name, the method signature, which will now be ()Ljava/lang/String; and the method call itself, which should be

jstring str = (jstring) env->CallObjectMethod(obj, mID);

Now, call setText(String, int), giving it as parameters the int that task3 receives and the jstring obtained from getInput. There is an easy way to obtain the signature of a function by using javap. Call it like this:

javap -classpath $WORKSPACE/Lab5_Tasks/app/build/intermediates/classes/flavor1/debug/osp/lab5/tasks -p -s osp.lab5.tasks.MainActivity

javap is used to disassemble java files, and the -s parameter is used to generate the signatures for public functions and fields, -p tells it to also generate signatures for private members of the class. Remember to call ReleaseStringUTFChars after you are done with the string.

Task 6 - Call a static method (1p)

This time, you have to call the getInt method and then check the result with checkInt. These are static methods. Accessing static methods is very similar to accessing instance methods. There are two changes: instead of using GetMethodID, use GetStaticMethodID, and when calling the function use CallStatic<Type>Method, passing the class instead of an object as first parameter.

Task 7 - Get the value of a field (1p)

Get value of the field “field” and return it. Getting the value of field means calling

jint i = env->GetIntField(obj, fID);

where obj is the object and fID is a jfieldID containing the ID of the field. To get the field ID, you need the class, obtained like in the previous exercises and to call

jfieldID fID = env->GetFieldID(cls, "field", "I");

where, cls is the jclass, “field” is the field name and “I” is the signature. Field signatures can also be obtained with javap (or by checking the JNI Types Documentation).

Task 8 - Set the value of a field (1p)

Set the value of the field “field” with the value received as a parameter. Instead of calling GetIntField, you will have to call

env->SetIntField(obj, fID, i);

with i being the value you wish to set the field to.

Task 9 - Generate an exception (1p)

Generate an exception. To generate an exception you have to get the class of the exception and then call

env->ThrowNew(exception, "JNI Generated Exception");

where exception is the class of the exception. The string provides additional information.

Task 10 - Check for an exception (1p)

Call the generateException method and check if it generated an exception. Return JNI_TRUE if yes, and JNI_FALSE otherwise. To check for an exception you can either call

jboolean ex = env->ExceptionCheck();

which returns JNI_TRUE if an exception has occurred and JNI_FALSE otherwise or

jthrowable ex = env->ExceptionOccured();

and check if ex is null or not. Remember to clear exception status, otherwise it will be thrown back to the JVM when the function returns.

env->ExceptionClear();

Task 11 - Accessing an array (1p)

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).

Task 12 - Keeping a reference to the array (1p)

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.

Task 13 - Direct pointer to array (1p)

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:

  • 0 - Copy back the content and free directArray
  • JNI_COMMIT - Copy the content but don't free directArray
  • JNI_ABORT - Free directArray

Copying is only done if directArray is a copy.

Modify the updateArray method to use direct copies of the array if possible.

osp/lectures/lecture-ndk.1475656872.txt.gz · Last modified: 2016/10/05 11:41 by laura.gheorghe
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0