Qt on Android: How to run C++ code on Android UI thread Useful features you need on Android that don't have a Qt API
I’d like to start a new series of Qt on Android articles, these will be small articles which will focus on useful features that you’ll need on Android but which don’t have any Qt API (yet). I’ll start with two pretty useful functions. These functions will help us to run C++ code directly on Android UI thread without writing any Java code. Qt 5.7 will bring will bring the same functionality.
Until Qt 5.7 is out, we need to use our own implementation, let’s check the code: androidutils.h
namespace KDAB { namespace Android { typedef std::function<void()> Runnable; /// Posts a runnable on Android thread, then exists. /// If you call runOnAndroidThread from Android UI thread, /// it will execute the runnable immediately void runOnAndroidThread(const Runnable &runnable); /// Posts a runnable on Android thread then waits until it's executed. void runOnAndroidThreadSync(const Runnable &runnable, int waitMs = INT_MAX); } // namespace Android } // KDAB
I think the comments are more than enough to understand the code. Let’s check the implementation: androidutils.cpp
namespace KDAB { namespace Android { static std::deque<Runnable> s_pendingRunnables; static std::mutex s_pendingRunnablesMutex; void runOnAndroidThread(const Runnable &runnable) { s_pendingRunnablesMutex.lock(); bool triggerRun = s_pendingRunnables.empty(); s_pendingRunnables.push_back(runnable); s_pendingRunnablesMutex.unlock(); if (triggerRun) { QtAndroid::androidActivity().callMethod<void>("runOnUiThread", "(Ljava/lang/Runnable;)V", QAndroidJniObject("com/kdab/android/utils/Runnable").object()); } } void runOnAndroidThreadSync(const Runnable &runnable, int waitMs) { std::shared_ptr<QSemaphore> sem = std::make_shared<QSemaphore>(); runOnAndroidThread([sem, &runnable](){ runnable(); sem->release(); }); sem->tryAcquire(1, waitMs); } extern "C" JNIEXPORT void JNICALL Java_com_kdab_android_utils_Runnable_runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/) { for (;;) { // run all posted runnables s_pendingRunnablesMutex.lock(); if (s_pendingRunnables.empty()) { s_pendingRunnablesMutex.unlock(); break; } Runnable runnable(std::move(s_pendingRunnables.front())); s_pendingRunnables.pop_front(); s_pendingRunnablesMutex.unlock(); runnable(); } } } // namespace Android } // KDAB
Let’s take a closer look at the source code:
- runOnAndroidThread enqueues the runnable in s_pendingRunnables deque, and if it’s the only runnable in deque, it calls Activity.runOnUiThread with our custom Runnable (it’s source code is listed below). This runnable is picked up by Android UI event loop and is executed on Android UI thread. Our custom Runnable just calls runPendingCppRunnables native function. For more information Qt on Android thread, you need to check How to access and use Android Java API from your Qt on Android using JNI in a safe way article.
- runOnAndroidThreadSync uses a semaphore to wait until the runnable is executed then exists.
- Java_com_kdab_android_utils_Runnable_runPendingCppRunnables runs all pending C++ runnables on Android UI thread.
And finally, let’s check our custom Runnable: Runnable.java
package com.kdab.android.utils; class Runnable implements java.lang.Runnable { @Override public void run() { runPendingCppRunnables(); } public static native void runPendingCppRunnables(); }
The implementation is very easy, the run function just calls runPendingCppRunnables native method which we’ll run all C++ runnables on Android UI thread.
How to use these functions? Well, it’s pretty easy:
First step is to add KDAB’s Android utils to your project.
- clone KDAB’s Android repository ( $ git clone https://github.com/KDAB/android.git )
- copy the contents of utils folder to your project (androidutils.cpp, androidutils.h and android folder)
- add them to your project
QT += androidextras SOURCES += androidutils.cpp HEADERS += androidutils.h ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
Let’s create a Qt Button class (which just wraps an Android Button Java object).
QAndroidJniObject m_button; // declared in your .h file // Implementation Button::Button { KDAB::Android::runOnAndroidThreadSync([this]{ m_button = QAndroidJniObject("android/widget/Button", "(Landroid/content/Context;)V", QtAndroid::androidActivity().object()); }); }
We need to use runOnAndroidThreadSync to make sure m_button is initialized properly in our C++ constructor. Let’s check QAndroidJniObject parameters:
- “android/widget/Button” – is the fully qualified class name
- “(Landroid/content/Context;)V” – is the Button constructor signature.
- QtAndroid::androidActivity().object() – is our activity
Let’s set a property
void Button::setText(const QString &text) { KDAB::Android::runOnAndroidThread([text, this]{ m_button.callMethod<void>("setText", "(Ljava/lang/CharSequence;)V", QAndroidJniObject::fromString(text).object()); }); }
We don’t need to wait until the property is set, therefore we can use runOnAndroidThread in this case. Because runOnAndroidThread is asynchronous, we must copy all the captured values! Otherwise they will be invalid when the runnable is executed on Android UI thread.
Let’s take a look to callMethod parameters:
- “setText” – is the method name
- “(Ljava/lang/CharSequence;)V” – is the method signature
- QAndroidJniObject::fromString(text).object() – is the string that we want to set
Let’s get a property
QString Button::text() const { QString res; KDAB::Android::runOnAndroidThreadSync([&res, this]{ res = m_button.callObjectMethod("getText", "()Ljava/lang/CharSequence;").toString(); }); return res; }
Because runOnAndroidThreadSync waits, we can capture res variable by reference, this way we can safely set the value on Android UI thread and return it on Qt thread.
Let’s check callObjectMethod parameters:
- “getText” – is the method name
- “()Ljava/lang/CharSequence;” – is the method signature
.toString() converts a java string to a QString.
You can download the source code from here : https://github.com/KDAB/android
Now that 5.7 is out, how do we do this?
After Qt 5.7:
https://doc.qt.io/qt-5/qtandroid.html#runOnAndroidThread looks like it does what above code does.