QtOnAndroid

Using JNI with Qt on Android

QML Model-View Example | | Using JNI to run own Java code

JNI is the interface to access Android Java classes from native C/C++ code, hence the name Java Native Interface.

In Qt there is a wrapper to JNI, which is contained in the Qt Android Extras module.

Let’s consider we want to call a native Java method like Math.max.

Then we add the Android Extras module to the qmake project file:

QT += androidextras

And include the corresponding header file:

#include <QtAndroidExtras>

Then we can call a static Java method via a call to QAndroidJniObject::callStaticMethod<T>(...) with the following parameters:

  • its fully qualified Java class name like “java/lang/Math” (or “java.lang.Math”)
  • the method name, e.g. “max”
  • and its signature specified in JNI style “({parameter-types})result-type”, e.g. “(II)I” for two Java integer params and an integer result
  • T corresponds to the Java return type, e.g. jint for a Java integer

Example for calling Math.max:


   jint a = 1, b = 2;
   jint max = QAndroidJniObject::callStaticMethod<jint>("java/lang/Math", "max", "(II)I", a, b);
 

Now we would like to call a more complex static method that does not return a primitive type but a Java object. Then the static call is of the form:

QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod("class", "method", "signature", ...);

For example, we would like to call Environment.getExternalStorageDirectory(). This method returns the top-level path to the primary external storage of the app. The type of the returned object is a Java File. It is available since API level 1:

QAndroidJniObject primary = QAndroidJniObject::callStaticObjectMethod("android.os.Environment", "getExternalStorageDirectory", "()Ljava/io/File;");

On my Samsung S5 this yields “/storage/sdcard0″ as path the the app’s primary external storage. One cannot rely on a particular path, though, since the paths depend on particular manufacturer and OS type.

As another static caller example, we would like to call Environment.getExternalStoragePublicDirectory(“MyPublicDir”). This method call returns a public path on the primary external storage. The type of the returned object is a Java File. It is available since API level 8:

QAndroidJniEnvironment env;
jstring string = env->NewStringUTF("MyPublicDir");
QAndroidJniObject path = QAndroidJniObject::callStaticObjectMethod("android.os.Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", string);

A variant of the previous call, which returns the digital camera’s image directory (DCIM):

QAndroidJniObject object = QAndroidJniObject::getStaticObjectField<jstring>("android.os.Environment", "DIRECTORY_DCIM");
QAndroidJniObject dcim = QAndroidJniObject::callStaticObjectMethod("android.os.Environment", "getExternalStoragePublicDirectory", "(Ljava/lang/String;)Ljava/io/File;", object.object<jobject>());

If the method to be called is non-static, we need an instance of QAndroidJniObject to call the method on. Then the call is of the form:

QAndroidJniObject object = ...;
QAndroidJniObject result = object.callObjectMethod("method", "signature", ...);

For example, we would like to call Context.getPackageName(). This method returns the package name of the app. The type of the returned object is a Java String. It is available since API level 1.

But first we need to get a reference to a Java object to call the method on. The only object we can get a handle on is the app activity object itself. We get a reference to this object from the QtNative class by calling its static activity() method:

QAndroidJniObject activity =
   QAndroidJniObject::callStaticObjectMethod(
      "org/qtproject/qt5/android/QtNative",
      "activity", "()Landroid/app/Activity;");

If everything went fine, the object’s property activity.isValid() indicates that the activity object could be referenced!

Since the android.app.Activity class is derived from android.content.Context, we can call the Content.getPackageName() method directly on the activity object:

QAndroidJniObject package = activity.callObjectMethod("getPackageName", "()Ljava/lang/String;");

Each QAndroidJniObject has a convenience method “toString”, which converts the contained Java object into a QString. If the type of the object is “java/lang/String” the object is converted to just that string (same holds for java.io.File).

In order to display the result of the previous getPackageName() method call, we convert it to a QString and pipe it into the qDebug() stream:

qDebug() << "package name:" + package.toString();

As next example, we would like to call Context.getExternalFilesDir(null). This method returns a private directory on the external primary storage. The type of the returned object is a Java File (available since API level 8):

QAndroidJniObject dir = activity.callObjectMethod("getExternalFilesDir", "(Ljava/lang/String;)Ljava/io/File;", NULL);

As final example, we would like to call Context.getExternalFilesDirs(null). This method returns a list of private directories on the external primary or secondary storage. The type of the returned object is an array of Java Files (available since API level 19). The first array element corresponds to a private path on the primary external storage (the internal SD card). The second array element corresponds to a private path on the secondary external storage (the SD card in the external SD card slot, if present), and so on…:

QAndroidJniObject dirs = activity.callObjectMethod("getExternalFilesDirs", "(Ljava/lang/String;)[Ljava/io/File;", NULL);

Since the returned object is a array and the method is only available in Kitkat (API level>=19), we need to perform some additional checks and calls to the JniEnvironment to extract an array element:

if (dirs.isValid())
{
   QAndroidJniEnvironment env;
   jsize l = env->GetArrayLength(dirs.object<jarray>());
   if (l>0)
   {
      QAndroidJniObject dir1 = env->GetObjectArrayElement(dirs.object<jobjectArray>(), 0);
      qDebug() << "external primary directory:" << dir1.toString();
   }
   if (l>1)
   {
      QAndroidJniObject dir2 = env->GetObjectArrayElement(dirs.object<jobjectArray>(), 1);
      qDebug() << "external secondary directory:" << dir2.toString();
   }
}


QML Model-View Example | | Using JNI to run own Java code

Options: