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

In order to successfully run an Android application with native support, you need to have Oracle Java 7 SDK installed and configure Android Studio to use that SDK.

Files

introapplication.zip tasks.zip

Abandon hope all ye who enter here for Android NDK is a mighty obstacle :D

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: introapplication.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.

Edit native.cpp and add the following function:

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

The equivalent C code is:

jstring Java_com_example_user_nativeapplication_MainActivity_getString(JNIEnv *env, jobject instance)
{
	return (*env)->NewStringUTF(env, "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-lib");
}
 
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().

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-lib.cpp. 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("com/example/user/test/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. 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();
osp/lectures/lecture-ndk.txt · Last modified: 2017/01/22 11:38 by laura.ruse
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