QtCon16 PDF
QtCon16 PDF
QtCon16 PDF
Qt on Android JNI
Qt Con, 2016
p.1
What's new in Qt 5.7 for Android
Android services
enable android.app.splash_screen_sticky
1 <activity ...>
2 <!-- ... -->
3 <!-- Splash screen -->
4 <meta-data android:name="android.app.splash_screen_drawable"
5 android:resource="@drawable/banner"/>
6 <meta-data android:name="android.app.splash_screen_sticky" android:value="true"/>
7 <!-- Splash screen -->
8 </activity>
1 #include <QGuiApplication>
2 #ifdef Q_OS_ANDROID
3 # include <QtAndroid>
4 #endif
5
6 int main(int argc, char *argv[])
7 {
8 //...
9 // Next line usually takes some time to complete
10 engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
11 #ifdef Q_OS_ANDROID
12 QtAndroid::hideSplashScreen();
13 #endif
14 return app.exec();
15 }
Android services
The problem:
Most of the Android Java methods (including the contructors) MUST be
called on Android UI thread.
but we want to call them from C++ (Qt thread)!
Qt 5.7 introduces:
typedef std::function Runnable;
void QtAndroid::runOnAndroidThread(const Runnable &runnable)
runs asynchronously the runable on Android UI thread
if it's called on the Android UI thread, it's executed immediately
usefull to call methods that don't return anything
void QtAndroid::runOnAndroidThreadSync(const Runnable
&runnable, int timeoutMs = INT_MAX)
runs the runable on Android UI thread
waits until it's executed or until timeoutMs has passed
useful to create objects, or get properties on Android UI thread
Android services
JNI is the Java Native Interface. It is needed to do calls to/from Java world
from/to native (C/C++) world.
using JNI functions where QAndroidExtras is not enough (e.g. access arrays)
Scalar types
C/C++ JNI Java Signature
uint8_t/unsigned char jboolean bool Z
int8_t/char/signed char jbyte byte B
uint16_t/unsigned short jchar char C
int16_t/short jshort short S
int32_t/int/(long) jint int I
int64_t/(long)/long long jlong long J
float jfloat float F
double jdouble double D
void void V
Arrays
JNI Java Signature
jbooleanArray bool[] [Z
jbyteArray byte[] [B
jcharArray char[] [C
jshortArray short[] [S
jintArray int[] [I
jlongArray long[] [L
jfloatArray float[] [F
jdoubleArray double[] [D
jarray type[] [Lfully/qualified/type/name;
jarray String[] [Ljava/lang/String;
1 //...
2 QtAndroid::runOnAndroidThread([on, FLAG_KEEP_SCREEN_ON] {
3 auto window = QtAndroid::androidActivity().callObjectMethod("getWindow",
4 "()Landroid/view/Window;");
5 //...
1 //...
2 auto window = QtAndroid::androidActivity().callObjectMethod("getWindow",...
3 if (on)
4 window.callMethod<void>("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
5 else
6 window.callMethod<void>("clearFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
7 }
8 }
Android Toasts are small popups which are used to show the user some
feedback. There are two ways to create Toast popups
After we have the Toast object, we just need to call Toast.show() method to
show it
1 enum Duration {
2 SHORT = 0,
3 LONG = 1
4 };
5
6 void showToast(const QString &message, Duration duration = LONG) {
7 QtAndroid::runOnAndroidThread([message, duration] {
8 auto javaString = QAndroidJniObject::fromString(message);
9 auto toast = QAndroidJniObject::callStaticObjectMethod("android/widget/Toast",
10 "makeText",
11 "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
12 QtAndroid::androidActivity().object(),
13 javaString.object(),
14 jint(duration));
15
16 toast.callMethod<void>("show");
17 });
18 }
1 enum Duration {
2 SHORT = 0,
3 LONG = 1
4 };
5
6 void showToast(const QString &message, Duration duration = LONG) {
7 QtAndroid::runOnAndroidThread([message, duration] {
8 //....
1 //...
2 auto javaString = QAndroidJniObject::fromString(message);
3 //...
1 //...
2 auto toast = QAndroidJniObject::callStaticObjectMethod("android/widget/Toast",
3 "makeText",
4 "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
5 QtAndroid::androidActivity().object(),
6 javaString.object(),
7 jint(duration));
8 //...
Use QAndroidJniObject
QAndroidJniObject::callStaticObjectMethod(const char *className,
const char *methodName, const char *signature, ...) to call Toast
Toast.makeText(Context context, CharSequence text, int duration)
Java static method, with the following params:
1 //...
2 toast.callMethod<void>("show");
3 });
4 }
Demo android/JNIIntro
Let's see what fibonacci function call looks like using the Qt androidextras
module.
1 # Changes to your .pro file
2 # ....
3 QT += androidextras
4 # ....
1 // C++ code
2 #include <QAndroidJniObject>
3 int fibonacci(int n)
4 {
5 return QAndroidJniObject::callStaticMethod<jint>
6 ("com/kdab/training/MyJavaClass" // java class name
7 , "fibonacci" // method name
8 , "(I)I" // signature
9 , n);
10 }
In order to access Java world from C/C++, using the old fashioned way, you'll
need to get access to two objects pointers:
JavaVM
needed to get a JNIEnv pointer
can be shared between threads
JNIEnv
provides most of the JNI functions
cannot be shared between threads
1 jint fibonacci(jint n)
2 {
3 JNIEnv* env = 0;
4
5 // Qt loop runs in a different thread than Android one.
6 // We must first attach the VM to that thread before we call anything.
7 if (s_javaVM->AttachCurrentThread(&env, NULL) < 0)
8 exit(0);
9
10 jint res = env->CallStaticIntMethod(s_myJavaClass, s_methodID, n);
11
12 s_javaVM->DetachCurrentThread();
13
14 return res;
15 }
As you can see it not that easy to call (just) a static Java function using the old
fashioned plain JNI APIs, and things will become even nastier when you have
to instantiate Java classes, and use Java objects from C/C++.
declare a native method in Java using native keyword (see slide 43)
1 // C++ code
2 #include <jni.h>
3
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7
8 JNIEXPORT void JNICALL
9 Java_com_kdab_training_MyJavaNatives_sendFibonaciResult(JNIEnv */*env*/,
10 jobject /*obj*/,
11 jint n)
12 {
13 qDebug() << "Computed fibonacci is:" << n;
14 }
15
16 #ifdef __cplusplus
17 }
18 #endif
Pro:
Con:
Step 3 find the ID of java class that declares these methods using
JniEnv::FindClass
Step 2 create a vector with all C/C++ methods that you want to register
1 // C++ code
2 #include <jni.h>
3
4 // define our native method
5 static void sendFibonaciResult(JNIEnv */*env*/, jobject /*obj*/, jint n)
6 {
7 qDebug() << "Computed fibonacci is:" << n;
8 }
9
10 // step 2
11 // create a vector with all our JNINativeMethod(s)
12 static JNINativeMethod methods[] = {
13 { "sendFibonaciResult", // const char* function name;
14 "(I)V", // const char* function signature
15 (void *)sendFibonaciResult // function pointer }
16 };
1 // step 1
2 // this method is called automatically by Java after the .so file is loaded
3 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/)
4 {
5 JNIEnv* env;
6 // get the JNIEnv pointer.
7 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
8 return JNI_ERR;
9
10 // step 3
11 // search for Java class which declares the native methods
12 jclass javaClass = env->FindClass("com/kdab/training/MyJavaNatives");
13 if (!javaClass)
14 return JNI_ERR;
15
16 // step 4
17 // register our native methods
18 if (env->RegisterNatives(javaClass, methods,
19 sizeof(methods) / sizeof(methods[0])) < 0) {
20 return JNI_ERR;
21 }
22 return JNI_VERSION_1_6;
23 }
Pro:
Con:
It's just a matter of taste which method you decide to use to register your
native functions.
We do recommend you to use JNIEnv::RegisterNatives as it offers you
extra protection because the VM checks the functions signature
Getting started
Every single Qt Android Service must have its own Service java class which
extends QtService
at boot time
on demand
set the same android.app.lib_name metadata for both service(s) & activity
elements
1 <service ... >
2 <!-- ... -->
3 <meta-data android:name="android.app.lib_name"
4 android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
5 <!-- ... -->
6 </service>
Getting started
repc generates source & replica (server & client) source files from .rep files
.rep file is the QtRemoteObjects IDL (interface description language)
Demo android/Service
http://www.kdab.com
qtonandroid@kdab.com
info@kdab.com
training@kdab.com
bogdan@kdab.com
p.67