Qt on Android Episode 5 - An introduction to JNI on Android, the Qt way
Update: Here you have also the Chinese version, thanks goes to Foruok.
After we’ve seen how to set up the development environment, how to use Qt on Android and what deployment systems are available and how to sign the final package, it’s time to move forward. In this article we are going to learn about JNI.
Why do we need JNI?
Because it is impossible for Qt to implement all Android features. To use any of them we’ll need to use JNI to access them. JNI the only way to do calls to/from Java world from/to native (C/C++) world.
JNI intro
In this article we’ll learn the basics of the JNI, in the next article(s) we’re going to learn how to use this knowledge to correctly extend Qt on Android apps. There is tons and tons of information on this matter on the net, in this article I’m focusing only on how to do JNI on Android using Qt. Starting with 5.2, Qt ships Qt Android Extras module. This module makes our life much more pleasant when we’ll have to do JNI.
There are two use cases:
- Call a Java function from C/C++
- Callback a C/C++ function from Java
Call a Java function from C/C++
Calling a Java function from C/C++ using Qt Android Extras module is quite easy.
First let’s create a Java static method:
// java file android/src/com/kdab/training/MyJavaClass.java package com.kdab.training; public class MyJavaClass { // this method will be called from C/C++ public static int fibonacci(int n) { if (n < 2) return n; return fibonacci(n-1) + fibonacci(n-2); } }
So, we defined fibonacci static method into MyJavaClass class into com.kdab.training package. This method returns the computed fibonacci number.
Now let’s see how to call it from Qt. The first step is to make sure we are using the android extras, so we need to add it to our .pro file.
# Changes to your .pro file # .... QT += androidextras # ....
Then do the actual call:
// C++ code #include <QAndroidJniObject> int fibonacci(int n) { return QAndroidJniObject::callStaticMethod<jint> ("com/kdab/training/MyJavaClass" // class name , "fibonacci" // method name , "(I)I" // signature , n); }
Yes, that’s all folks!
Let’s take a closer look to this code and see what we have here:
- we are using QAndroidJniObject::callStaticMethod to call a Java static method.
- first argument is the fully-qualified class name. The fully-qualified name for a class (or interface) is the package name (com/kdab/training) followed by the class/interface name(MyJavaClass), separated by a slash (/) (NOT by a period, the period is used only in Java world not in C/C++ world!)
- the next argument is the method name
- the next is the method signature
- the next arguments are the arguments that will be passed to the java function
Please check QAndroidJniObject documentation for more information about method signature and the arguments types.
Callback a C/C++ function from Java
In order to callback a C/C++ function from Java you need to do the follow steps:
- declare the native method(s) in Java, using native keyword
- register native method(s) in C/C++
- do the actual call
Declare the native method(s) in Java, using native keyword.
Let’s change the previous java code a little bit:
// java file android/src/com/kdab/training/MyJavaClass.java package com.kdab.training; class MyJavaNatives { // declare the native method public static native void sendFibonaciResult(int n); } public class MyJavaClass { // this method will be called from C/C++ public static int fibonacci(int n) { if (n < 2) return n; return fibonacci(n-1) + fibonacci(n-2); } // the second method that will be called from C/C++ public static void compute_fibonacci(int n) { // callback the native method with the computed result. MyJavaNatives.sendFibonaciResult(fibonacci(n)); } }
Let’s take a closer look to this code and see what we have here:
- personally, I prefer to keep all my native method(s) in a separate class, so, I declared sendFibonaciResult native method into MyJavaNatives class into com.kdab.taining package. This native method will be used by compute_fibonacci static method to callback the C/C++ world to send the computed result instead to return it as fibonacci method does.
- compute_fibonacci, this method will be called from C/C++, but instead to return the result as fibonacci method does, it uses sendFibonaciResult native method to callback the C/C++ world to end the result.
If you try only this code it will fail, because sendFibonaciResult is not registered, and Java VM doesn’t know it yet.
C/C++ register native methods
Now let’s see how to register the function in C/C++.
First thing you need to know is that you can register only function(s). You can NOT register C++ class (non-static) members!
There are two ways to register native methods, we are going to check both ot them:
Registering functions using Java_Fully_Qualified_ClassName_MethodName
#include <jni.h> #include <QDebug> #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv *env, jobject obj, jint n) { qDebug() << "Computed fibonacci is:" << n; } #ifdef __cplusplus } #endif
Let’s take a closer look to this code and see what we have here:
- First thing we’ve seen, is that all the functions must be exported as C functions and NOT as C++ functions!
- The function name MUST follow the next template: Java word, followed by the package name, then followed by the class name, then followed by the method name,separated by an underscore (_)
- When JavaVM loads the .so file, it will search for this template and it will automatically register all your functions for you
- the first argument (env) of the function is a pointer to JNIEnv object.
- the second argument (obj) is a reference to the Java object inside which this native method has been declared.
- the first and second arguments are mandatory for every function that you’ll register.
- the next arguments must match the java native method arguments. So, our third argument is actually our first (and only) java native method argument. This is the argument that compute_fibonacci will pass to sendFibonaciResult native method when it calls it.
Using this method to register is quite easy to declare and register, but it has a few disadvantages:
- the function names are huge: Java_com_kdab_training_MyJavaNatives_sendFibonaciResult
- the library must to export all functions
- unsafer, there is no way for the JavaVM to check the function signature, because the functions are exported as C functions and NOT as C++ functions!
Use JNIEnv::RegisterNatives to register native functions
In order to use JNIEnv::RegisterNatives to register native functions, we need to do the following 4 steps to use it:
- step 1: we need get access to an JNIEnv pointer. The easiest way is to define and and export JNI_OnLoad function, (once per .so file) in any .cpp file we like.
- step 2: create a vector with all C/C++ methods that we want to register.
- step 3: find the ID of java class that declares these methods using JniEnv::FindClass
- step 4: call JNIEnv::RegisterNatives(java_class_ID, methods_vector, n_methods)
// C++ code #include <jni.h> #include <QDebug> // define our native method static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n) { qDebug() << "Computed fibonacci is:" << n; } // step 2 // create a vector with all our JNINativeMethod(s) static JNINativeMethod methods[] = { { "sendFibonaciResult", // const char* function name; "(I)V", // const char* function signature (void *)fibonaciResult // function pointer } }; // step 1 // this method is called automatically by Java VM // after the .so file is loaded JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { JNIEnv* env; // get the JNIEnv pointer. if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } // step 3 // search for Java class which declares the native methods jclass javaClass = env->FindClass("com/kdab/training/MyJavaNatives"); if (!javaClass) return JNI_ERR; // step 4 // register our native methods if (env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) { return JNI_ERR; } return JNI_VERSION_1_6; }
Let’s check the code for a better understanding:
- static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n) it is the method that we’re registering and which Java VM will call.
- the first argument (env) of the function is a pointer to JNIEnv object.
- the second argument (obj) is a reference to the Java object inside which this native method has been declared.
- the first and second arguments are mandatory for every function that you’ll register.
- the next arguments must match the java native method arguments. So, our third argument is actually our first (and only) java native method argument. This is the argument that compute_fibonacci will pass to sendFibonaciResult native method when it calls it.
- we add the method to a vector (methods) of JNINativeMethod structures.
- JNINativeMethod structure has the following fields:
- const char* name – it is the function name, it has to be exactly as the native function name that we’ve declared in Java
- const char* signature – it is the function signature, it has to match the native function arguments that we’ve declared in Java
- void* fnPtr – it is the C/C++ function pointer. In our case it’s a pointer to static void fibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n) function. As you can see the C/C++ function name doesn’t matter, the Java VM needs only a pointer to it.
- the rest of the code is clear and easy and I don’t think it needs any explanations than the comments.
Even if it looks a little bit complicated to use it, this method has the following advantages:
- the C/C++ function can have any name you want
- the library needs to export only one function (JNI_OnLoad)
- safer, because the Java VM checks the declared signature
It’s just a matter of taste which method you decide to use to register your native functions. I do recommend you to use JNIEnv::RegisterNatives as it offers you extra protection because the Java VM checks the functions signature and it throws an exception if it doesn’t match.
Conclusion
In this article we’ve learned the basics of the JNI, in the next article(s) we’re going to learn how to use this knowledge to correctly extend Qt on Android apps. We’ll talk more about Qt on Android apps architecture, how to extend the Java part of your application and we’ll take a real life example to show how to correctly do safe calls from Qt thread to Android UI thread and vice-versa.
Thank you for your time!
Awesome! Thanks
Do you mean, for something as simple as getting the list of contacts it we need to create a Java wrapper first?
This dilutes the multiplatform experience of Qt.
You can use JNI from Qt as long as the class don’t need to be run on Android UI thread. Otherwise you’ll need to write a few lines of Java code.
BogDan, what do you mean? Cannot I use JNI to send a string from C++ to Java so to be displayed on Android UI?
If you’ll like to “show” the string from Qt Threat, no, it will not work.
Very informative, thanks.
Hi,BogDan,here is Chinese version: http://blog.csdn.net/foruok/article/details/41826085
hi,BogDan,I’d written a book about Qt and Qt on Android(in Chinese), and was published recently.You and your articles give me too much help,I’m more grateful for this than words can tell.Here is the link:http://product.china-pub.com/3804179.The book named “Qt on Android核心编程”,in this book I talked about a set of basic topics about Qt, and then focus on Qt on Android,include the internal mechanism,common Android topics in Qt.
Hi foruok,
Sorry for the slow reply.
I just want to say Congrats to you!
Could i find an example how to use QT and libusb for androids with file descriptors?
How can i pass the fd to Qt from Java, i think this is very important but i cannot find a useful example!
Hi! I try to find information on Ministro II compabitility with Android 5 Lollipop. We have a map/navigation software, based on QT and it uses Ministro on installation to download and install needed packages. Its been working great, but now we have a big problem because Ministro is not working with Android 5. Any help or glues? Or info if Ministro will be updated in near future?
Sadly, due to an Android bug, it is impossible to use Ministro on Android 5. Try to bundle Qt libs into your package.
Thanks BogDan! Sad to hear that, but anyhow we now know the problem and will see if we can bundle needed QT libs.
Hi BogDan Vatra,
I have one query regarding using JNI in android. Currently i am working on hybrid i.e Qt/QML application. I am calling one static method in java class from c++. In that static method i’m setting new content using m_instance.setContentView(R.layout.main). As i am running this on UI thread, it perfectly displays content from mail.xml. But after completing my task i am not able to unload this view and go back to Qml view. So my question is that, can i use UI loaded in native activity as well as in QML as per my need or am i doing something wrong??
Well first and foremost, you should not change the main Activity content view!
Instead try to add it as an native view (check http://doc.qt.io/qt-5/qwindow.html#fromWinId ) on top of your QML surface. Then you can set it’s visibility and position easily.
Hi BogDan,
i read the documentation link you provided but i’m not getting what exactly it trying to say. From where i can get WId.
I think with method you mentioned i will get problem mentioned here https://bugreports.qt.io/browse/QTBUG-40159.
It tells to create a empty view.
Can you provide some sample.?
Actually my task is to select multiple images from gallery. So i tried this with different intent parameters like Intent.EXTRA_ALLOW_MULTIPLE and other but some apps allows to select multiple images and some not. I have tried with Intent.ACTION_OPEN_DOCUMENT in introduce in android kitkat but that too has problems with samung devices like samsung galaxy s4. I need to select multiple image in all device. So i’m trying select images with Gridview in java but for that i have to change the activity content. If i change, i will not get QML view back.
A fix for QTBUG-40159 is scheduled for 5.6
Hi BogDan!
I have a problem. In Java side, I’m subscribe on ACTION_HEADSET_PLUG and receive it successfull. How do I correctly using JNI (may be without using JNI), pass this event into QML-item?
Sorry for my English
Hi BogDan;
I wonder how to call Java codes from C++ / Qt without QtAndroidExtras? I want to use jni.h file for this job. Or how does QtAndroidExtras in Android? Thanks.