Qt Android: Native UI and Qt/C++ over JNI
-
Hi guys,
I am currently working on a hybrid Android/Qt application that would use the Android Java APIs to build the UI while using JNI to communicate with C++ libraries built with Qt.
My understanding is that Qt does not live in the main Android UI thread but creates a dedicated thread to run the main() method that is in the developer's source code, effectively becoming the Qt "main" thread.
Although I see the motivation here ("Code once, run everywhere..."), this two-threads setup is really inconvenient as I need the Java side to call Qt/C++ APIs via JNI and reversely, which would be way better without having to deal with threading issues.In that spirit, I have created a custom implementation of QAbstractEventDispatcher based on Android NDK's ALooper set of APIs (which allow me to register the file descriptors from Qt socket notifiers) and some additional JNI wrapping an Android Handler for managing timers. Thanks to that dispatcher, everything "lives" on the Android UI thread.
Additionally, by posting messages to a Handler from the Application::onCreate() lifecycle callbacks, I manage to create a nested execution of the Android eventloop (by calling Looper.loop()), and that way manage to get control over the event-loop in my QCoreApplication::exec(), called from my application "main" function. This UI thread eventloop hijacking works well, the native Android UI remains super responsive and I dont get any Application Not Responding errors \o/.
At this point, I'm wondering why did the Qt port not go this way and instead creates a second thread for handling the Qt side of things, is anybody aware of any caveats I should be worried about ?
Thanks to this approach, methods such as QtAndroid::runOnAndroidThread / QtAndroid::runOnAndroidThreadSync would become useless, probably reducing bugs as well as the overall learning curve of Qt on Android.
My main problem now is that a lot of static variables initialization (mainly a pointer to the global JavaVM* and JNIEnv* for the Android UI thread takes place in/around the QtActivity (as per my inspection of Qt's source code) which makes it impossible to use really helpful classes from QtAndroidExtras module such as QAndroidJniObject without first creating the QtActivity. As I explained in my introduction, my goal is to build an Android-native UI with Java APIs, so creating that activity does not work for my usecase.
Is there any reason why not to perform the initialization of the JavaVM and JNIEnv pointers from the QtApplication class ? That way, one could simply initialize the QtApplication and then use whatever he sees fit in terms of UI, including plain old Android Java activities.
I believe Qt is an amazing framework, so far it has really helped and allowed me to successfully build cross-platform projects without a sweat. On the Android platform however, I think the current architecture is really limiting as not using QML or Qt widgets is a very valid usecase, just as on every other supported platform.
I have a version of that very project for iOS (which uses the native iOS UIKit Objective-C APIs for the UI), macOS (using Cocoa for the UI) and the APIs work seamlessly on the main thread, no need for any extra threads, there is only one "main"/UI thread. I understand that the Android app lifecycle is different but still, that would be worthy on Android as well!
I'm wondering if I am the only one facing this limitation and whether anything is in the works on that matter. Moreover, given the open governance of the project would a patch aiming at fixing those limitations be considered for adoption in the mainline project ?
Thanks in advance for your precious responses, dear community!
-
Interesting idea!
It would be great if you could post that to the Qt mailing list(s):
Android Development: android-development@qt-project.org
(General Development: development@qt-project.org)Those are the places where the developers hang out. This forum is more like a place for Qt users..it's more likely that you get answers to your questions on the mailing list(s) :)