Skip to content

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:

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:

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

FacebookTwitterLinkedInEmail

Categories: Android blogs / KDAB Blogs / KDAB on Qt

Tags: /

2 thoughts on “Qt on Android: How to run C++ code on Android UI thread”

Leave a Reply

Your email address will not be published. Required fields are marked *