Qt android: interface C++ and java through JNI

The latest revisions Qt framework allow to port application developed in C++ code to run in Android OS also. For standard app the Qt framework provide all the required features but in some case is necessary to interact with the system using native java code. For make such task Qt provide some specific objects able to allow a more easy work.


Create java class

First point is to create a java source file with inside our java class we'll use for interact with the system. An example of such class can be as follow:

public class MyJavaClass
{
    private final Activity m_MainActivity;

    public MyJavaClass(final Activity MainActivity)
    {
        // Save the main activity pointer
        m_MainActivity = MainActivity;
    }
}

Please note, although not necessary in this example we'll pass as class constructor param the pointer to the main activity object created by the Qt app. This because this object will be necessary in case you need to make calls to some native views. Indeed if you want to interact with native views it's usually require to call view function from UI thread. This class and all call made from Qt are not part of UI thread than many of then will dont' work. The common workaround is to use the following "wrapper" code:

m_MainActivity.runOnUiThread(new Runnable()
{
    @Override
    public void run()
    {
        // Here you can call directly native views
    }
});

As you can  note we use the main activity object just saved to run a code in a UI thread for allow operation with native views. For allow Qt Creator to compile and include this java class inside final apk you have to insert into your .pro project file as follow:

ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
DISTFILES += android/AndroidManifest.xml
OTHER_FILES += android/src/com/mycompanyname/myappname/MyJavaClass.java

An important point you must to know is if you want Qt Creator to correctly compile your java file you have to put the file in the subfolder path /src/com/mycompanyname/myappname/ starting from the point where you place the AndroidManifest.xml file (the android folder in our example). Once created your java class file you can load it from C++ Qt side as follow:

QAndroidJniObject MyJavaClass;

MyJavaClass = QAndroidJniObject("com/mycompanyname/myappname/MyJavaClass",
                                "(Landroid/app/Activity;)V",
                                QtAndroid::androidActivity().object<jobject>()
                                );

Now we have a Qt object to use for communicate with our java class just loaded. Note the param to pass the main activity pointer to the class as explained above.

From C++ to java

Once loaded the class as just explained we can call java methods from C++ code. At first we'll add the java method to call from C++ as follow:

public class MyJavaClass
{
    private final Activity m_MainActivity;

    public MyJavaClass(final Activity MainActivity)
    {
        // Save the main activity pointer
        m_MainActivity = MainActivity;
    }

    public void TestCallMe(int param1, string param2)
    {
        // Do something here
    }
}

As you can see a new function called TestCallMe has been added with two example params, one integer and one string. Using Qt class is possible to call this method from C++ side as follow:

int Param1;
QString Param2;

MyJavaClass.callMethod<void>("TestCallMe",
                             "(ILjava/lang/String;)V",
                             Param1,
                             QAndroidJniObject::fromString(Param2).object<jstring>()
                             );

Basically here we have to specify, in add of method name, the params accepted using the JNI format specification. You can read more about the syntax here. Native variable like integer doesn't need specific conversion but some more complex element, like QString as in example, require a conversion to the JNI native format jstring. Anyway reading the JNI types map it will be possible to exchange the majority of common format available.

From java to C++

Here the process is a bit more complex. At first we need to define a native function declaration inside java code. For convenience we'll create a dedicated java class as container for native functions as follow:

class MainQtApp
{
    public static native void nativeQtTestFuction(int param1);
}

This can be used for invoke function connection from java code in the form:

MainQtApp.nativeQtTestFuction(1234);

This is all from java side since all the job is made from C++ side. Basically we have to include in our C++ code a special function called from JNI engine at startup where is registered inside the same  JNI engine a "connection" between the java functions and the C++ function. The following code will explain better:

static void QtTestFuction(JNIEnv *env, jobject obj, jint Param1)
{
    // Here call Qt object
}

static JNINativeMethod methodsArray[] =
{
    {"nativeQtTestFuction", "(I)V", (void *)QtTestFuction},
};

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    jclass javaClass;
    JNIEnv* env;

    if(vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK)
    {
        return JNI_ERR;
    }

    javaClass = env->FindClass("com/mycompanyname/myappname/MainQtApp");
    if(!javaClass)
    {
        return JNI_ERR;
    }

    if(env->RegisterNatives(javaClass, methodsArray, sizeof(methodsArray) / sizeof(methodsArray[0])) < 0)
    {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

As you can see the function JNI_OnLoad() is called automatically at app startup. Inside, after some validity check, the code look for the "container" class MainQtApp with inside the java native methods. Once found register the connection between java and C++ code using the special array JNINativeMethod containing the string name of java method with the corresponding params in JNI format, as explained above, and the C++ function code to call. Please note the function is in C format since is more easy to pass a static function pointer through the array and also because JNI manage only C format (this is a very important point to remember). From inside the C function you can call directly your Qt object class using different methods. It's all, not very difficult but a bit annoying to develop.


Comments

  1. I am trying to use QtAndroid JNI but it isn't working. Maybe I am making the string array wrong?

    QAndroidJniEnvironment env;
    QAndroidJniObject string = QAndroidJniObject::fromString(filename);
    QAndroidJniObject stringArray = env->NewObjectArray(1, env->FindClass("java/lang/String"), NULL);
    env->SetObjectArrayElement(stringArray, 0, string.object());

    QAndroidJniObject::callStaticMethod("android/media/MediaScannerConnection",
    "scanFile",
    "(Landroid/content/Context;[Ljava/lang/String;[Ljava/lang/String;Landroid/media/MediaScannerConnection/OnScanCompletedListener)V",
    QtAndroid::androidActivity().object(), stringArray, 0, 0);

    ReplyDelete
  2. Is correct to use the character '[' inside this param string?

    Landroid/content/Context;[Ljava/lang/String;[Ljava/lang/String;

    ReplyDelete
  3. Very useful! Thank you!

    ReplyDelete

Post a Comment

Popular posts from this blog

Access GPIO from Linux user space

Launch an app from Android shell terminal

Qt 5 and " Failed to load platform plugin 'windows' " error