Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
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:
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.
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:
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:
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:
.toString() converts a java string to a QString.
You can download the source code from here : https://github.com/KDAB/android
Wrapping tiny JS libraries in QML to do quick and simple things effortlessly and elegantly.
Factors to Consider When Choosing a Software Stack
A New Qt (5.x & 6.x) Library for Complete Shared Storage Access
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Upgrade your applications from Qt 5 to Qt 6 with KDAB’s migration services. Get a free migration assessment and join a hands-on workshop to prepare your team for a successful transition!
Learn more
2 Comments
7 - Apr - 2017
Jason
Now that 5.7 is out, how do we do this?
10 - Oct - 2019
Jan
After Qt 5.7: https://doc.qt.io/qt-5/qtandroid.html#runOnAndroidThread looks like it does what above code does.